~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_knit.py

  • Committer: Ian Clatworthy
  • Date: 2007-08-14 03:59:22 UTC
  • mto: (2733.1.1 ianc-integration)
  • mto: This revision was merged to the branch mainline in revision 2734.
  • Revision ID: ian.clatworthy@internode.on.net-20070814035922-siavg542cwvkf4r5
Fix pretty doc generation so works for all html docs

Show diffs side-by-side

added added

removed removed

Lines of Context:
26
26
    errors,
27
27
    generate_ids,
28
28
    knit,
 
29
    pack,
29
30
    )
30
31
from bzrlib.errors import (
31
32
    RevisionAlreadyPresent,
33
34
    RevisionNotPresent,
34
35
    NoSuchFile,
35
36
    )
 
37
from bzrlib.index import *
36
38
from bzrlib.knit import (
37
39
    KnitContent,
 
40
    KnitGraphIndex,
38
41
    KnitVersionedFile,
39
42
    KnitPlainFactory,
40
43
    KnitAnnotateFactory,
 
44
    _KnitAccess,
41
45
    _KnitData,
42
46
    _KnitIndex,
 
47
    _PackAccess,
43
48
    WeaveToKnit,
 
49
    KnitSequenceMatcher,
44
50
    )
45
51
from bzrlib.osutils import split_lines
46
 
from bzrlib.tests import TestCase, TestCaseWithTransport, Feature
 
52
from bzrlib.tests import (
 
53
    Feature,
 
54
    TestCase,
 
55
    TestCaseWithMemoryTransport,
 
56
    TestCaseWithTransport,
 
57
    )
47
58
from bzrlib.transport import TransportLogger, get_transport
48
59
from bzrlib.transport.memory import MemoryTransport
49
60
from bzrlib.weave import Weave
142
153
        return queue_call
143
154
 
144
155
 
 
156
class KnitRecordAccessTestsMixin(object):
 
157
    """Tests for getting and putting knit records."""
 
158
 
 
159
    def assertAccessExists(self, access):
 
160
        """Ensure the data area for access has been initialised/exists."""
 
161
        raise NotImplementedError(self.assertAccessExists)
 
162
 
 
163
    def test_add_raw_records(self):
 
164
        """Add_raw_records adds records retrievable later."""
 
165
        access = self.get_access()
 
166
        memos = access.add_raw_records([10], '1234567890')
 
167
        self.assertEqual(['1234567890'], list(access.get_raw_records(memos)))
 
168
 
 
169
    def test_add_several_raw_records(self):
 
170
        """add_raw_records with many records and read some back."""
 
171
        access = self.get_access()
 
172
        memos = access.add_raw_records([10, 2, 5], '12345678901234567')
 
173
        self.assertEqual(['1234567890', '12', '34567'],
 
174
            list(access.get_raw_records(memos)))
 
175
        self.assertEqual(['1234567890'],
 
176
            list(access.get_raw_records(memos[0:1])))
 
177
        self.assertEqual(['12'],
 
178
            list(access.get_raw_records(memos[1:2])))
 
179
        self.assertEqual(['34567'],
 
180
            list(access.get_raw_records(memos[2:3])))
 
181
        self.assertEqual(['1234567890', '34567'],
 
182
            list(access.get_raw_records(memos[0:1] + memos[2:3])))
 
183
 
 
184
    def test_create(self):
 
185
        """create() should make a file on disk."""
 
186
        access = self.get_access()
 
187
        access.create()
 
188
        self.assertAccessExists(access)
 
189
 
 
190
    def test_open_file(self):
 
191
        """open_file never errors."""
 
192
        access = self.get_access()
 
193
        access.open_file()
 
194
 
 
195
 
 
196
class TestKnitKnitAccess(TestCaseWithMemoryTransport, KnitRecordAccessTestsMixin):
 
197
    """Tests for the .kndx implementation."""
 
198
 
 
199
    def assertAccessExists(self, access):
 
200
        self.assertNotEqual(None, access.open_file())
 
201
 
 
202
    def get_access(self):
 
203
        """Get a .knit style access instance."""
 
204
        access = _KnitAccess(self.get_transport(), "foo.knit", None, None,
 
205
            False, False)
 
206
        return access
 
207
    
 
208
 
 
209
class TestPackKnitAccess(TestCaseWithMemoryTransport, KnitRecordAccessTestsMixin):
 
210
    """Tests for the pack based access."""
 
211
 
 
212
    def assertAccessExists(self, access):
 
213
        # as pack based access has no backing unless an index maps data, this
 
214
        # is a no-op.
 
215
        pass
 
216
 
 
217
    def get_access(self):
 
218
        return self._get_access()[0]
 
219
 
 
220
    def _get_access(self, packname='packfile', index='FOO'):
 
221
        transport = self.get_transport()
 
222
        def write_data(bytes):
 
223
            transport.append_bytes(packname, bytes)
 
224
        writer = pack.ContainerWriter(write_data)
 
225
        writer.begin()
 
226
        indices = {index:(transport, packname)}
 
227
        access = _PackAccess(indices, writer=(writer, index))
 
228
        return access, writer
 
229
 
 
230
    def test_read_from_several_packs(self):
 
231
        access, writer = self._get_access()
 
232
        memos = []
 
233
        memos.extend(access.add_raw_records([10], '1234567890'))
 
234
        writer.end()
 
235
        access, writer = self._get_access('pack2', 'FOOBAR')
 
236
        memos.extend(access.add_raw_records([5], '12345'))
 
237
        writer.end()
 
238
        access, writer = self._get_access('pack3', 'BAZ')
 
239
        memos.extend(access.add_raw_records([5], 'alpha'))
 
240
        writer.end()
 
241
        transport = self.get_transport()
 
242
        access = _PackAccess({"FOO":(transport, 'packfile'),
 
243
            "FOOBAR":(transport, 'pack2'),
 
244
            "BAZ":(transport, 'pack3')})
 
245
        self.assertEqual(['1234567890', '12345', 'alpha'],
 
246
            list(access.get_raw_records(memos)))
 
247
        self.assertEqual(['1234567890'],
 
248
            list(access.get_raw_records(memos[0:1])))
 
249
        self.assertEqual(['12345'],
 
250
            list(access.get_raw_records(memos[1:2])))
 
251
        self.assertEqual(['alpha'],
 
252
            list(access.get_raw_records(memos[2:3])))
 
253
        self.assertEqual(['1234567890', 'alpha'],
 
254
            list(access.get_raw_records(memos[0:1] + memos[2:3])))
 
255
 
 
256
    def test_set_writer(self):
 
257
        """The writer should be settable post construction."""
 
258
        access = _PackAccess({})
 
259
        transport = self.get_transport()
 
260
        packname = 'packfile'
 
261
        index = 'foo'
 
262
        def write_data(bytes):
 
263
            transport.append_bytes(packname, bytes)
 
264
        writer = pack.ContainerWriter(write_data)
 
265
        writer.begin()
 
266
        access.set_writer(writer, index, (transport, packname))
 
267
        memos = access.add_raw_records([10], '1234567890')
 
268
        writer.end()
 
269
        self.assertEqual(['1234567890'], list(access.get_raw_records(memos)))
 
270
 
 
271
 
145
272
class LowLevelKnitDataTests(TestCase):
146
273
 
147
274
    def create_gz_content(self, text):
159
286
                                        'end rev-id-1\n'
160
287
                                        % (sha1sum,))
161
288
        transport = MockTransport([gz_txt])
162
 
        data = _KnitData(transport, 'filename', mode='r')
163
 
        records = [('rev-id-1', 0, len(gz_txt))]
 
289
        access = _KnitAccess(transport, 'filename', None, None, False, False)
 
290
        data = _KnitData(access=access)
 
291
        records = [('rev-id-1', (None, 0, len(gz_txt)))]
164
292
 
165
293
        contents = data.read_records(records)
166
294
        self.assertEqual({'rev-id-1':(['foo\n', 'bar\n'], sha1sum)}, contents)
176
304
                                        'end rev-id-1\n'
177
305
                                        % (sha1sum,))
178
306
        transport = MockTransport([gz_txt])
179
 
        data = _KnitData(transport, 'filename', mode='r')
180
 
        records = [('rev-id-1', 0, len(gz_txt))]
 
307
        access = _KnitAccess(transport, 'filename', None, None, False, False)
 
308
        data = _KnitData(access=access)
 
309
        records = [('rev-id-1', (None, 0, len(gz_txt)))]
181
310
        self.assertRaises(errors.KnitCorrupt, data.read_records, records)
182
311
 
183
312
        # read_records_iter_raw won't detect that sort of mismatch/corruption
193
322
                                        'end rev-id-1\n'
194
323
                                        % (sha1sum,))
195
324
        transport = MockTransport([gz_txt])
196
 
        data = _KnitData(transport, 'filename', mode='r')
197
 
        records = [('rev-id-1', 0, len(gz_txt))]
 
325
        access = _KnitAccess(transport, 'filename', None, None, False, False)
 
326
        data = _KnitData(access=access)
 
327
        records = [('rev-id-1', (None, 0, len(gz_txt)))]
198
328
        self.assertRaises(errors.KnitCorrupt, data.read_records, records)
199
329
 
200
330
        # read_records_iter_raw won't detect that sort of mismatch/corruption
209
339
                                        'end rev-id-1\n'
210
340
                                        % (sha1sum,))
211
341
        transport = MockTransport([gz_txt])
212
 
        data = _KnitData(transport, 'filename', mode='r')
 
342
        access = _KnitAccess(transport, 'filename', None, None, False, False)
 
343
        data = _KnitData(access=access)
213
344
        # We are asking for rev-id-2, but the data is rev-id-1
214
 
        records = [('rev-id-2', 0, len(gz_txt))]
 
345
        records = [('rev-id-2', (None, 0, len(gz_txt)))]
215
346
        self.assertRaises(errors.KnitCorrupt, data.read_records, records)
216
347
 
217
348
        # read_records_iter_raw will notice if we request the wrong version.
226
357
               'end rev-id-1\n'
227
358
               % (sha1sum,))
228
359
        transport = MockTransport([txt])
229
 
        data = _KnitData(transport, 'filename', mode='r')
230
 
        records = [('rev-id-1', 0, len(txt))]
 
360
        access = _KnitAccess(transport, 'filename', None, None, False, False)
 
361
        data = _KnitData(access=access)
 
362
        records = [('rev-id-1', (None, 0, len(txt)))]
231
363
 
232
364
        # We don't have valid gzip data ==> corrupt
233
365
        self.assertRaises(errors.KnitCorrupt, data.read_records, records)
246
378
        # Change 2 bytes in the middle to \xff
247
379
        gz_txt = gz_txt[:10] + '\xff\xff' + gz_txt[12:]
248
380
        transport = MockTransport([gz_txt])
249
 
        data = _KnitData(transport, 'filename', mode='r')
250
 
        records = [('rev-id-1', 0, len(gz_txt))]
 
381
        access = _KnitAccess(transport, 'filename', None, None, False, False)
 
382
        data = _KnitData(access=access)
 
383
        records = [('rev-id-1', (None, 0, len(gz_txt)))]
251
384
 
252
385
        self.assertRaises(errors.KnitCorrupt, data.read_records, records)
253
386
 
356
489
            ])
357
490
        index = self.get_knit_index(transport, "filename", "r")
358
491
        self.assertEqual(2, index.num_versions())
359
 
        self.assertEqual(1, index.lookup("version"))
360
 
        self.assertEqual((3, 4), index.get_position("version"))
 
492
        # check that the index used is the first one written. (Specific
 
493
        # to KnitIndex style indices.
 
494
        self.assertEqual("1", index._version_list_to_index(["version"]))
 
495
        self.assertEqual((None, 3, 4), index.get_position("version"))
361
496
        self.assertEqual(["options3"], index.get_options("version"))
362
497
        self.assertEqual(["parent", "other"],
363
498
            index.get_parents_with_ghosts("version"))
380
515
            _KnitIndex.HEADER
381
516
            ])
382
517
        index = self.get_knit_index(transport, "filename", "r")
383
 
        index.add_version(utf8_revision_id, ["option"], 0, 1, [])
 
518
        index.add_version(utf8_revision_id, ["option"], (None, 0, 1), [])
384
519
        self.assertEqual(("append_bytes", ("filename",
385
520
            "\n%s option 0 1  :" % (utf8_revision_id,)),
386
521
            {}),
393
528
            _KnitIndex.HEADER
394
529
            ])
395
530
        index = self.get_knit_index(transport, "filename", "r")
396
 
        index.add_version("version", ["option"], 0, 1, [utf8_revision_id])
 
531
        index.add_version("version", ["option"], (None, 0, 1), [utf8_revision_id])
397
532
        self.assertEqual(("append_bytes", ("filename",
398
533
            "\nversion option 0 1 .%s :" % (utf8_revision_id,)),
399
534
            {}),
404
539
        index = self.get_knit_index(transport, "filename", "w", create=True)
405
540
        self.assertEqual([], index.get_graph())
406
541
 
407
 
        index.add_version("a", ["option"], 0, 1, ["b"])
 
542
        index.add_version("a", ["option"], (None, 0, 1), ["b"])
408
543
        self.assertEqual([("a", ["b"])], index.get_graph())
409
544
 
410
 
        index.add_version("c", ["option"], 0, 1, ["d"])
 
545
        index.add_version("c", ["option"], (None, 0, 1), ["d"])
411
546
        self.assertEqual([("a", ["b"]), ("c", ["d"])],
412
547
            sorted(index.get_graph()))
413
548
 
460
595
        self.assertRaises(RevisionNotPresent,
461
596
            index.get_ancestry_with_ghosts, ["e"])
462
597
 
 
598
    def test_iter_parents(self):
 
599
        transport = MockTransport()
 
600
        index = self.get_knit_index(transport, "filename", "w", create=True)
 
601
        # no parents
 
602
        index.add_version('r0', ['option'], (None, 0, 1), [])
 
603
        # 1 parent
 
604
        index.add_version('r1', ['option'], (None, 0, 1), ['r0'])
 
605
        # 2 parents
 
606
        index.add_version('r2', ['option'], (None, 0, 1), ['r1', 'r0'])
 
607
        # XXX TODO a ghost
 
608
        # cases: each sample data individually:
 
609
        self.assertEqual(set([('r0', ())]),
 
610
            set(index.iter_parents(['r0'])))
 
611
        self.assertEqual(set([('r1', ('r0', ))]),
 
612
            set(index.iter_parents(['r1'])))
 
613
        self.assertEqual(set([('r2', ('r1', 'r0'))]),
 
614
            set(index.iter_parents(['r2'])))
 
615
        # no nodes returned for a missing node
 
616
        self.assertEqual(set(),
 
617
            set(index.iter_parents(['missing'])))
 
618
        # 1 node returned with missing nodes skipped
 
619
        self.assertEqual(set([('r1', ('r0', ))]),
 
620
            set(index.iter_parents(['ghost1', 'r1', 'ghost'])))
 
621
        # 2 nodes returned
 
622
        self.assertEqual(set([('r0', ()), ('r1', ('r0', ))]),
 
623
            set(index.iter_parents(['r0', 'r1'])))
 
624
        # 2 nodes returned, missing skipped
 
625
        self.assertEqual(set([('r0', ()), ('r1', ('r0', ))]),
 
626
            set(index.iter_parents(['a', 'r0', 'b', 'r1', 'c'])))
 
627
 
463
628
    def test_num_versions(self):
464
629
        transport = MockTransport([
465
630
            _KnitIndex.HEADER
469
634
        self.assertEqual(0, index.num_versions())
470
635
        self.assertEqual(0, len(index))
471
636
 
472
 
        index.add_version("a", ["option"], 0, 1, [])
473
 
        self.assertEqual(1, index.num_versions())
474
 
        self.assertEqual(1, len(index))
475
 
 
476
 
        index.add_version("a", ["option2"], 1, 2, [])
477
 
        self.assertEqual(1, index.num_versions())
478
 
        self.assertEqual(1, len(index))
479
 
 
480
 
        index.add_version("b", ["option"], 0, 1, [])
 
637
        index.add_version("a", ["option"], (None, 0, 1), [])
 
638
        self.assertEqual(1, index.num_versions())
 
639
        self.assertEqual(1, len(index))
 
640
 
 
641
        index.add_version("a", ["option2"], (None, 1, 2), [])
 
642
        self.assertEqual(1, index.num_versions())
 
643
        self.assertEqual(1, len(index))
 
644
 
 
645
        index.add_version("b", ["option"], (None, 0, 1), [])
481
646
        self.assertEqual(2, index.num_versions())
482
647
        self.assertEqual(2, len(index))
483
648
 
489
654
 
490
655
        self.assertEqual([], index.get_versions())
491
656
 
492
 
        index.add_version("a", ["option"], 0, 1, [])
493
 
        self.assertEqual(["a"], index.get_versions())
494
 
 
495
 
        index.add_version("a", ["option"], 0, 1, [])
496
 
        self.assertEqual(["a"], index.get_versions())
497
 
 
498
 
        index.add_version("b", ["option"], 0, 1, [])
 
657
        index.add_version("a", ["option"], (None, 0, 1), [])
 
658
        self.assertEqual(["a"], index.get_versions())
 
659
 
 
660
        index.add_version("a", ["option"], (None, 0, 1), [])
 
661
        self.assertEqual(["a"], index.get_versions())
 
662
 
 
663
        index.add_version("b", ["option"], (None, 0, 1), [])
499
664
        self.assertEqual(["a", "b"], index.get_versions())
500
665
 
501
 
    def test_idx_to_name(self):
502
 
        transport = MockTransport([
503
 
            _KnitIndex.HEADER,
504
 
            "a option 0 1 :",
505
 
            "b option 0 1 :"
506
 
            ])
507
 
        index = self.get_knit_index(transport, "filename", "r")
508
 
 
509
 
        self.assertEqual("a", index.idx_to_name(0))
510
 
        self.assertEqual("b", index.idx_to_name(1))
511
 
        self.assertEqual("b", index.idx_to_name(-1))
512
 
        self.assertEqual("a", index.idx_to_name(-2))
513
 
 
514
 
    def test_lookup(self):
515
 
        transport = MockTransport([
516
 
            _KnitIndex.HEADER,
517
 
            "a option 0 1 :",
518
 
            "b option 0 1 :"
519
 
            ])
520
 
        index = self.get_knit_index(transport, "filename", "r")
521
 
 
522
 
        self.assertEqual(0, index.lookup("a"))
523
 
        self.assertEqual(1, index.lookup("b"))
524
 
 
525
666
    def test_add_version(self):
526
667
        transport = MockTransport([
527
668
            _KnitIndex.HEADER
528
669
            ])
529
670
        index = self.get_knit_index(transport, "filename", "r")
530
671
 
531
 
        index.add_version("a", ["option"], 0, 1, ["b"])
 
672
        index.add_version("a", ["option"], (None, 0, 1), ["b"])
532
673
        self.assertEqual(("append_bytes",
533
674
            ("filename", "\na option 0 1 .b :"),
534
675
            {}), transport.calls.pop(0))
535
676
        self.assertTrue(index.has_version("a"))
536
677
        self.assertEqual(1, index.num_versions())
537
 
        self.assertEqual((0, 1), index.get_position("a"))
 
678
        self.assertEqual((None, 0, 1), index.get_position("a"))
538
679
        self.assertEqual(["option"], index.get_options("a"))
539
680
        self.assertEqual(["b"], index.get_parents_with_ghosts("a"))
540
681
 
541
 
        index.add_version("a", ["opt"], 1, 2, ["c"])
 
682
        index.add_version("a", ["opt"], (None, 1, 2), ["c"])
542
683
        self.assertEqual(("append_bytes",
543
684
            ("filename", "\na opt 1 2 .c :"),
544
685
            {}), transport.calls.pop(0))
545
686
        self.assertTrue(index.has_version("a"))
546
687
        self.assertEqual(1, index.num_versions())
547
 
        self.assertEqual((1, 2), index.get_position("a"))
 
688
        self.assertEqual((None, 1, 2), index.get_position("a"))
548
689
        self.assertEqual(["opt"], index.get_options("a"))
549
690
        self.assertEqual(["c"], index.get_parents_with_ghosts("a"))
550
691
 
551
 
        index.add_version("b", ["option"], 2, 3, ["a"])
 
692
        index.add_version("b", ["option"], (None, 2, 3), ["a"])
552
693
        self.assertEqual(("append_bytes",
553
694
            ("filename", "\nb option 2 3 0 :"),
554
695
            {}), transport.calls.pop(0))
555
696
        self.assertTrue(index.has_version("b"))
556
697
        self.assertEqual(2, index.num_versions())
557
 
        self.assertEqual((2, 3), index.get_position("b"))
 
698
        self.assertEqual((None, 2, 3), index.get_position("b"))
558
699
        self.assertEqual(["option"], index.get_options("b"))
559
700
        self.assertEqual(["a"], index.get_parents_with_ghosts("b"))
560
701
 
565
706
        index = self.get_knit_index(transport, "filename", "r")
566
707
 
567
708
        index.add_versions([
568
 
            ("a", ["option"], 0, 1, ["b"]),
569
 
            ("a", ["opt"], 1, 2, ["c"]),
570
 
            ("b", ["option"], 2, 3, ["a"])
 
709
            ("a", ["option"], (None, 0, 1), ["b"]),
 
710
            ("a", ["opt"], (None, 1, 2), ["c"]),
 
711
            ("b", ["option"], (None, 2, 3), ["a"])
571
712
            ])
572
713
        self.assertEqual(("append_bytes", ("filename",
573
714
            "\na option 0 1 .b :"
577
718
        self.assertTrue(index.has_version("a"))
578
719
        self.assertTrue(index.has_version("b"))
579
720
        self.assertEqual(2, index.num_versions())
580
 
        self.assertEqual((1, 2), index.get_position("a"))
581
 
        self.assertEqual((2, 3), index.get_position("b"))
 
721
        self.assertEqual((None, 1, 2), index.get_position("a"))
 
722
        self.assertEqual((None, 2, 3), index.get_position("b"))
582
723
        self.assertEqual(["opt"], index.get_options("a"))
583
724
        self.assertEqual(["option"], index.get_options("b"))
584
725
        self.assertEqual(["c"], index.get_parents_with_ghosts("a"))
593
734
        self.assertEqual([], transport.calls)
594
735
 
595
736
        index.add_versions([
596
 
            ("a", ["option"], 0, 1, ["b"]),
597
 
            ("a", ["opt"], 1, 2, ["c"]),
598
 
            ("b", ["option"], 2, 3, ["a"])
 
737
            ("a", ["option"], (None, 0, 1), ["b"]),
 
738
            ("a", ["opt"], (None, 1, 2), ["c"]),
 
739
            ("b", ["option"], (None, 2, 3), ["a"])
599
740
            ])
600
741
        name, (filename, f), kwargs = transport.calls.pop(0)
601
742
        self.assertEqual("put_file_non_atomic", name)
628
769
            ])
629
770
        index = self.get_knit_index(transport, "filename", "r")
630
771
 
631
 
        self.assertEqual((0, 1), index.get_position("a"))
632
 
        self.assertEqual((1, 2), index.get_position("b"))
 
772
        self.assertEqual((None, 0, 1), index.get_position("a"))
 
773
        self.assertEqual((None, 1, 2), index.get_position("b"))
633
774
 
634
775
    def test_get_method(self):
635
776
        transport = MockTransport([
845
986
class KnitTests(TestCaseWithTransport):
846
987
    """Class containing knit test helper routines."""
847
988
 
848
 
    def make_test_knit(self, annotate=False, delay_create=False):
 
989
    def make_test_knit(self, annotate=False, delay_create=False, index=None):
849
990
        if not annotate:
850
991
            factory = KnitPlainFactory()
851
992
        else:
852
993
            factory = None
853
994
        return KnitVersionedFile('test', get_transport('.'), access_mode='w',
854
995
                                 factory=factory, create=True,
855
 
                                 delay_create=delay_create)
 
996
                                 delay_create=delay_create, index=index)
856
997
 
857
998
 
858
999
class BasicKnitTests(KnitTests):
865
1006
        """Construct empty k"""
866
1007
        self.make_test_knit()
867
1008
 
 
1009
    def test_make_explicit_index(self):
 
1010
        """We can supply an index to use."""
 
1011
        knit = KnitVersionedFile('test', get_transport('.'),
 
1012
            index='strangelove')
 
1013
        self.assertEqual(knit._index, 'strangelove')
 
1014
 
868
1015
    def test_knit_add(self):
869
1016
        """Store one text in knit and retrieve"""
870
1017
        k = self.make_test_knit()
916
1063
    def test_delta(self):
917
1064
        """Expression of knit delta as lines"""
918
1065
        k = self.make_test_knit()
 
1066
        KnitContent
919
1067
        td = list(line_delta(TEXT_1.splitlines(True),
920
1068
                             TEXT_1A.splitlines(True)))
921
1069
        self.assertEqualDiff(''.join(td), delta_1_1a)
922
1070
        out = apply_line_delta(TEXT_1.splitlines(True), td)
923
1071
        self.assertEqualDiff(''.join(out), TEXT_1A)
924
1072
 
 
1073
    def assertDerivedBlocksEqual(self, source, target, noeol=False):
 
1074
        """Assert that the derived matching blocks match real output"""
 
1075
        source_lines = source.splitlines(True)
 
1076
        target_lines = target.splitlines(True)
 
1077
        def nl(line):
 
1078
            if noeol and not line.endswith('\n'):
 
1079
                return line + '\n'
 
1080
            else:
 
1081
                return line
 
1082
        source_content = KnitContent([(None, nl(l)) for l in source_lines])
 
1083
        target_content = KnitContent([(None, nl(l)) for l in target_lines])
 
1084
        line_delta = source_content.line_delta(target_content)
 
1085
        delta_blocks = list(KnitContent.get_line_delta_blocks(line_delta,
 
1086
            source_lines, target_lines))
 
1087
        matcher = KnitSequenceMatcher(None, source_lines, target_lines)
 
1088
        matcher_blocks = list(list(matcher.get_matching_blocks()))
 
1089
        self.assertEqual(matcher_blocks, delta_blocks)
 
1090
 
 
1091
    def test_get_line_delta_blocks(self):
 
1092
        self.assertDerivedBlocksEqual('a\nb\nc\n', 'q\nc\n')
 
1093
        self.assertDerivedBlocksEqual(TEXT_1, TEXT_1)
 
1094
        self.assertDerivedBlocksEqual(TEXT_1, TEXT_1A)
 
1095
        self.assertDerivedBlocksEqual(TEXT_1, TEXT_1B)
 
1096
        self.assertDerivedBlocksEqual(TEXT_1B, TEXT_1A)
 
1097
        self.assertDerivedBlocksEqual(TEXT_1A, TEXT_1B)
 
1098
        self.assertDerivedBlocksEqual(TEXT_1A, '')
 
1099
        self.assertDerivedBlocksEqual('', TEXT_1A)
 
1100
        self.assertDerivedBlocksEqual('', '')
 
1101
        self.assertDerivedBlocksEqual('a\nb\nc', 'a\nb\nc\nd')
 
1102
 
 
1103
    def test_get_line_delta_blocks_noeol(self):
 
1104
        """Handle historical knit deltas safely
 
1105
 
 
1106
        Some existing knit deltas don't consider the last line to differ
 
1107
        when the only difference whether it has a final newline.
 
1108
 
 
1109
        New knit deltas appear to always consider the last line to differ
 
1110
        in this case.
 
1111
        """
 
1112
        self.assertDerivedBlocksEqual('a\nb\nc', 'a\nb\nc\nd\n', noeol=True)
 
1113
        self.assertDerivedBlocksEqual('a\nb\nc\nd\n', 'a\nb\nc', noeol=True)
 
1114
        self.assertDerivedBlocksEqual('a\nb\nc\n', 'a\nb\nc', noeol=True)
 
1115
        self.assertDerivedBlocksEqual('a\nb\nc', 'a\nb\nc\n', noeol=True)
 
1116
 
925
1117
    def test_add_with_parents(self):
926
1118
        """Store in knit with parents"""
927
1119
        k = self.make_test_knit()
943
1135
        k.clear_cache()
944
1136
        self.assertEqualDiff(''.join(k.get_lines('text-1a')), TEXT_1A)
945
1137
 
 
1138
    def test_add_delta_knit_graph_index(self):
 
1139
        """Does adding work with a KnitGraphIndex."""
 
1140
        index = InMemoryGraphIndex(2)
 
1141
        knit_index = KnitGraphIndex(index, add_callback=index.add_nodes,
 
1142
            deltas=True)
 
1143
        k = KnitVersionedFile('test', get_transport('.'),
 
1144
            delta=True, create=True, index=knit_index)
 
1145
        self.add_stock_one_and_one_a(k)
 
1146
        k.clear_cache()
 
1147
        self.assertEqualDiff(''.join(k.get_lines('text-1a')), TEXT_1A)
 
1148
        # check the index had the right data added.
 
1149
        self.assertEqual(set([
 
1150
            (index, ('text-1', ), ' 0 127', ((), ())),
 
1151
            (index, ('text-1a', ), ' 127 140', ((('text-1', ),), (('text-1', ),))),
 
1152
            ]), set(index.iter_all_entries()))
 
1153
        # we should not have a .kndx file
 
1154
        self.assertFalse(get_transport('.').has('test.kndx'))
 
1155
 
946
1156
    def test_annotate(self):
947
1157
        """Annotations"""
948
1158
        k = KnitVersionedFile('knit', get_transport('.'), factory=KnitAnnotateFactory(),
1381
1591
 
1382
1592
        def read_one_raw(version):
1383
1593
            pos_map = k._get_components_positions([version])
1384
 
            method, pos, size, next = pos_map[version]
1385
 
            lst = list(k._data.read_records_iter_raw([(version, pos, size)]))
 
1594
            method, index_memo, next = pos_map[version]
 
1595
            lst = list(k._data.read_records_iter_raw([(version, index_memo)]))
1386
1596
            self.assertEqual(1, len(lst))
1387
1597
            return lst[0]
1388
1598
 
1402
1612
 
1403
1613
        def read_one(version):
1404
1614
            pos_map = k._get_components_positions([version])
1405
 
            method, pos, size, next = pos_map[version]
1406
 
            lst = list(k._data.read_records_iter([(version, pos, size)]))
 
1615
            method, index_memo, next = pos_map[version]
 
1616
            lst = list(k._data.read_records_iter([(version, index_memo)]))
1407
1617
            self.assertEqual(1, len(lst))
1408
1618
            return lst[0]
1409
1619
 
1447
1657
        """Adding versions to the index should update the lookup dict"""
1448
1658
        knit = self.make_test_knit()
1449
1659
        idx = knit._index
1450
 
        idx.add_version('a-1', ['fulltext'], 0, 0, [])
 
1660
        idx.add_version('a-1', ['fulltext'], (None, 0, 0), [])
1451
1661
        self.check_file_contents('test.kndx',
1452
1662
            '# bzr knit index 8\n'
1453
1663
            '\n'
1454
1664
            'a-1 fulltext 0 0  :'
1455
1665
            )
1456
 
        idx.add_versions([('a-2', ['fulltext'], 0, 0, ['a-1']),
1457
 
                          ('a-3', ['fulltext'], 0, 0, ['a-2']),
 
1666
        idx.add_versions([('a-2', ['fulltext'], (None, 0, 0), ['a-1']),
 
1667
                          ('a-3', ['fulltext'], (None, 0, 0), ['a-2']),
1458
1668
                         ])
1459
1669
        self.check_file_contents('test.kndx',
1460
1670
            '# bzr knit index 8\n'
1483
1693
 
1484
1694
        knit = self.make_test_knit()
1485
1695
        idx = knit._index
1486
 
        idx.add_version('a-1', ['fulltext'], 0, 0, [])
 
1696
        idx.add_version('a-1', ['fulltext'], (None, 0, 0), [])
1487
1697
 
1488
1698
        class StopEarly(Exception):
1489
1699
            pass
1490
1700
 
1491
1701
        def generate_failure():
1492
1702
            """Add some entries and then raise an exception"""
1493
 
            yield ('a-2', ['fulltext'], 0, 0, ['a-1'])
1494
 
            yield ('a-3', ['fulltext'], 0, 0, ['a-2'])
 
1703
            yield ('a-2', ['fulltext'], (None, 0, 0), ['a-1'])
 
1704
            yield ('a-3', ['fulltext'], (None, 0, 0), ['a-2'])
1495
1705
            raise StopEarly()
1496
1706
 
1497
1707
        # Assert the pre-condition
1519
1729
        t.put_bytes('test.kndx', '# not really a knit header\n\n')
1520
1730
 
1521
1731
        self.assertRaises(KnitHeaderError, self.make_test_knit)
 
1732
 
 
1733
 
 
1734
class TestGraphIndexKnit(KnitTests):
 
1735
    """Tests for knits using a GraphIndex rather than a KnitIndex."""
 
1736
 
 
1737
    def make_g_index(self, name, ref_lists=0, nodes=[]):
 
1738
        builder = GraphIndexBuilder(ref_lists)
 
1739
        for node, references, value in nodes:
 
1740
            builder.add_node(node, references, value)
 
1741
        stream = builder.finish()
 
1742
        trans = self.get_transport()
 
1743
        trans.put_file(name, stream)
 
1744
        return GraphIndex(trans, name)
 
1745
 
 
1746
    def two_graph_index(self, deltas=False, catch_adds=False):
 
1747
        """Build a two-graph index.
 
1748
 
 
1749
        :param deltas: If true, use underlying indices with two node-ref
 
1750
            lists and 'parent' set to a delta-compressed against tail.
 
1751
        """
 
1752
        # build a complex graph across several indices.
 
1753
        if deltas:
 
1754
            # delta compression inn the index
 
1755
            index1 = self.make_g_index('1', 2, [
 
1756
                (('tip', ), 'N0 100', ([('parent', )], [], )),
 
1757
                (('tail', ), '', ([], []))])
 
1758
            index2 = self.make_g_index('2', 2, [
 
1759
                (('parent', ), ' 100 78', ([('tail', ), ('ghost', )], [('tail', )])),
 
1760
                (('separate', ), '', ([], []))])
 
1761
        else:
 
1762
            # just blob location and graph in the index.
 
1763
            index1 = self.make_g_index('1', 1, [
 
1764
                (('tip', ), 'N0 100', ([('parent', )], )),
 
1765
                (('tail', ), '', ([], ))])
 
1766
            index2 = self.make_g_index('2', 1, [
 
1767
                (('parent', ), ' 100 78', ([('tail', ), ('ghost', )], )),
 
1768
                (('separate', ), '', ([], ))])
 
1769
        combined_index = CombinedGraphIndex([index1, index2])
 
1770
        if catch_adds:
 
1771
            self.combined_index = combined_index
 
1772
            self.caught_entries = []
 
1773
            add_callback = self.catch_add
 
1774
        else:
 
1775
            add_callback = None
 
1776
        return KnitGraphIndex(combined_index, deltas=deltas,
 
1777
            add_callback=add_callback)
 
1778
 
 
1779
    def test_get_graph(self):
 
1780
        index = self.two_graph_index()
 
1781
        self.assertEqual(set([
 
1782
            ('tip', ('parent', )),
 
1783
            ('tail', ()),
 
1784
            ('parent', ('tail', 'ghost')),
 
1785
            ('separate', ()),
 
1786
            ]), set(index.get_graph()))
 
1787
 
 
1788
    def test_get_ancestry(self):
 
1789
        # get_ancestry is defined as eliding ghosts, not erroring.
 
1790
        index = self.two_graph_index()
 
1791
        self.assertEqual([], index.get_ancestry([]))
 
1792
        self.assertEqual(['separate'], index.get_ancestry(['separate']))
 
1793
        self.assertEqual(['tail'], index.get_ancestry(['tail']))
 
1794
        self.assertEqual(['tail', 'parent'], index.get_ancestry(['parent']))
 
1795
        self.assertEqual(['tail', 'parent', 'tip'], index.get_ancestry(['tip']))
 
1796
        self.assertTrue(index.get_ancestry(['tip', 'separate']) in
 
1797
            (['tail', 'parent', 'tip', 'separate'],
 
1798
             ['separate', 'tail', 'parent', 'tip'],
 
1799
            ))
 
1800
        # and without topo_sort
 
1801
        self.assertEqual(set(['separate']),
 
1802
            set(index.get_ancestry(['separate'], topo_sorted=False)))
 
1803
        self.assertEqual(set(['tail']),
 
1804
            set(index.get_ancestry(['tail'], topo_sorted=False)))
 
1805
        self.assertEqual(set(['tail', 'parent']),
 
1806
            set(index.get_ancestry(['parent'], topo_sorted=False)))
 
1807
        self.assertEqual(set(['tail', 'parent', 'tip']),
 
1808
            set(index.get_ancestry(['tip'], topo_sorted=False)))
 
1809
        self.assertEqual(set(['separate', 'tail', 'parent', 'tip']),
 
1810
            set(index.get_ancestry(['tip', 'separate'])))
 
1811
        # asking for a ghost makes it go boom.
 
1812
        self.assertRaises(errors.RevisionNotPresent, index.get_ancestry, ['ghost'])
 
1813
 
 
1814
    def test_get_ancestry_with_ghosts(self):
 
1815
        index = self.two_graph_index()
 
1816
        self.assertEqual([], index.get_ancestry_with_ghosts([]))
 
1817
        self.assertEqual(['separate'], index.get_ancestry_with_ghosts(['separate']))
 
1818
        self.assertEqual(['tail'], index.get_ancestry_with_ghosts(['tail']))
 
1819
        self.assertTrue(index.get_ancestry_with_ghosts(['parent']) in
 
1820
            (['tail', 'ghost', 'parent'],
 
1821
             ['ghost', 'tail', 'parent'],
 
1822
            ))
 
1823
        self.assertTrue(index.get_ancestry_with_ghosts(['tip']) in
 
1824
            (['tail', 'ghost', 'parent', 'tip'],
 
1825
             ['ghost', 'tail', 'parent', 'tip'],
 
1826
            ))
 
1827
        self.assertTrue(index.get_ancestry_with_ghosts(['tip', 'separate']) in
 
1828
            (['tail', 'ghost', 'parent', 'tip', 'separate'],
 
1829
             ['ghost', 'tail', 'parent', 'tip', 'separate'],
 
1830
             ['separate', 'tail', 'ghost', 'parent', 'tip'],
 
1831
             ['separate', 'ghost', 'tail', 'parent', 'tip'],
 
1832
            ))
 
1833
        # asking for a ghost makes it go boom.
 
1834
        self.assertRaises(errors.RevisionNotPresent, index.get_ancestry_with_ghosts, ['ghost'])
 
1835
 
 
1836
    def test_num_versions(self):
 
1837
        index = self.two_graph_index()
 
1838
        self.assertEqual(4, index.num_versions())
 
1839
 
 
1840
    def test_get_versions(self):
 
1841
        index = self.two_graph_index()
 
1842
        self.assertEqual(set(['tail', 'tip', 'parent', 'separate']),
 
1843
            set(index.get_versions()))
 
1844
 
 
1845
    def test_has_version(self):
 
1846
        index = self.two_graph_index()
 
1847
        self.assertTrue(index.has_version('tail'))
 
1848
        self.assertFalse(index.has_version('ghost'))
 
1849
 
 
1850
    def test_get_position(self):
 
1851
        index = self.two_graph_index()
 
1852
        self.assertEqual((index._graph_index._indices[0], 0, 100), index.get_position('tip'))
 
1853
        self.assertEqual((index._graph_index._indices[1], 100, 78), index.get_position('parent'))
 
1854
 
 
1855
    def test_get_method_deltas(self):
 
1856
        index = self.two_graph_index(deltas=True)
 
1857
        self.assertEqual('fulltext', index.get_method('tip'))
 
1858
        self.assertEqual('line-delta', index.get_method('parent'))
 
1859
 
 
1860
    def test_get_method_no_deltas(self):
 
1861
        # check that the parent-history lookup is ignored with deltas=False.
 
1862
        index = self.two_graph_index(deltas=False)
 
1863
        self.assertEqual('fulltext', index.get_method('tip'))
 
1864
        self.assertEqual('fulltext', index.get_method('parent'))
 
1865
 
 
1866
    def test_get_options_deltas(self):
 
1867
        index = self.two_graph_index(deltas=True)
 
1868
        self.assertEqual(['fulltext', 'no-eol'], index.get_options('tip'))
 
1869
        self.assertEqual(['line-delta'], index.get_options('parent'))
 
1870
 
 
1871
    def test_get_options_no_deltas(self):
 
1872
        # check that the parent-history lookup is ignored with deltas=False.
 
1873
        index = self.two_graph_index(deltas=False)
 
1874
        self.assertEqual(['fulltext', 'no-eol'], index.get_options('tip'))
 
1875
        self.assertEqual(['fulltext'], index.get_options('parent'))
 
1876
 
 
1877
    def test_get_parents(self):
 
1878
        # get_parents ignores ghosts
 
1879
        index = self.two_graph_index()
 
1880
        self.assertEqual(('tail', ), index.get_parents('parent'))
 
1881
        # and errors on ghosts.
 
1882
        self.assertRaises(errors.RevisionNotPresent,
 
1883
            index.get_parents, 'ghost')
 
1884
 
 
1885
    def test_get_parents_with_ghosts(self):
 
1886
        index = self.two_graph_index()
 
1887
        self.assertEqual(('tail', 'ghost'), index.get_parents_with_ghosts('parent'))
 
1888
        # and errors on ghosts.
 
1889
        self.assertRaises(errors.RevisionNotPresent,
 
1890
            index.get_parents_with_ghosts, 'ghost')
 
1891
 
 
1892
    def test_check_versions_present(self):
 
1893
        # ghosts should not be considered present
 
1894
        index = self.two_graph_index()
 
1895
        self.assertRaises(RevisionNotPresent, index.check_versions_present,
 
1896
            ['ghost'])
 
1897
        self.assertRaises(RevisionNotPresent, index.check_versions_present,
 
1898
            ['tail', 'ghost'])
 
1899
        index.check_versions_present(['tail', 'separate'])
 
1900
 
 
1901
    def catch_add(self, entries):
 
1902
        self.caught_entries.append(entries)
 
1903
 
 
1904
    def test_add_no_callback_errors(self):
 
1905
        index = self.two_graph_index()
 
1906
        self.assertRaises(errors.ReadOnlyError, index.add_version,
 
1907
            'new', 'fulltext,no-eol', (None, 50, 60), ['separate'])
 
1908
 
 
1909
    def test_add_version_smoke(self):
 
1910
        index = self.two_graph_index(catch_adds=True)
 
1911
        index.add_version('new', 'fulltext,no-eol', (None, 50, 60), ['separate'])
 
1912
        self.assertEqual([[(('new', ), 'N50 60', ((('separate',),),))]],
 
1913
            self.caught_entries)
 
1914
 
 
1915
    def test_add_version_delta_not_delta_index(self):
 
1916
        index = self.two_graph_index(catch_adds=True)
 
1917
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
1918
            'new', 'no-eol,line-delta', (None, 0, 100), ['parent'])
 
1919
        self.assertEqual([], self.caught_entries)
 
1920
 
 
1921
    def test_add_version_same_dup(self):
 
1922
        index = self.two_graph_index(catch_adds=True)
 
1923
        # options can be spelt two different ways
 
1924
        index.add_version('tip', 'fulltext,no-eol', (None, 0, 100), ['parent'])
 
1925
        index.add_version('tip', 'no-eol,fulltext', (None, 0, 100), ['parent'])
 
1926
        # but neither should have added data.
 
1927
        self.assertEqual([[], []], self.caught_entries)
 
1928
        
 
1929
    def test_add_version_different_dup(self):
 
1930
        index = self.two_graph_index(deltas=True, catch_adds=True)
 
1931
        # change options
 
1932
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
1933
            'tip', 'no-eol,line-delta', (None, 0, 100), ['parent'])
 
1934
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
1935
            'tip', 'line-delta,no-eol', (None, 0, 100), ['parent'])
 
1936
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
1937
            'tip', 'fulltext', (None, 0, 100), ['parent'])
 
1938
        # position/length
 
1939
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
1940
            'tip', 'fulltext,no-eol', (None, 50, 100), ['parent'])
 
1941
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
1942
            'tip', 'fulltext,no-eol', (None, 0, 1000), ['parent'])
 
1943
        # parents
 
1944
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
1945
            'tip', 'fulltext,no-eol', (None, 0, 100), [])
 
1946
        self.assertEqual([], self.caught_entries)
 
1947
        
 
1948
    def test_add_versions_nodeltas(self):
 
1949
        index = self.two_graph_index(catch_adds=True)
 
1950
        index.add_versions([
 
1951
                ('new', 'fulltext,no-eol', (None, 50, 60), ['separate']),
 
1952
                ('new2', 'fulltext', (None, 0, 6), ['new']),
 
1953
                ])
 
1954
        self.assertEqual([(('new', ), 'N50 60', ((('separate',),),)),
 
1955
            (('new2', ), ' 0 6', ((('new',),),))],
 
1956
            sorted(self.caught_entries[0]))
 
1957
        self.assertEqual(1, len(self.caught_entries))
 
1958
 
 
1959
    def test_add_versions_deltas(self):
 
1960
        index = self.two_graph_index(deltas=True, catch_adds=True)
 
1961
        index.add_versions([
 
1962
                ('new', 'fulltext,no-eol', (None, 50, 60), ['separate']),
 
1963
                ('new2', 'line-delta', (None, 0, 6), ['new']),
 
1964
                ])
 
1965
        self.assertEqual([(('new', ), 'N50 60', ((('separate',),), ())),
 
1966
            (('new2', ), ' 0 6', ((('new',),), (('new',),), ))],
 
1967
            sorted(self.caught_entries[0]))
 
1968
        self.assertEqual(1, len(self.caught_entries))
 
1969
 
 
1970
    def test_add_versions_delta_not_delta_index(self):
 
1971
        index = self.two_graph_index(catch_adds=True)
 
1972
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
1973
            [('new', 'no-eol,line-delta', (None, 0, 100), ['parent'])])
 
1974
        self.assertEqual([], self.caught_entries)
 
1975
 
 
1976
    def test_add_versions_same_dup(self):
 
1977
        index = self.two_graph_index(catch_adds=True)
 
1978
        # options can be spelt two different ways
 
1979
        index.add_versions([('tip', 'fulltext,no-eol', (None, 0, 100), ['parent'])])
 
1980
        index.add_versions([('tip', 'no-eol,fulltext', (None, 0, 100), ['parent'])])
 
1981
        # but neither should have added data.
 
1982
        self.assertEqual([[], []], self.caught_entries)
 
1983
        
 
1984
    def test_add_versions_different_dup(self):
 
1985
        index = self.two_graph_index(deltas=True, catch_adds=True)
 
1986
        # change options
 
1987
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
1988
            [('tip', 'no-eol,line-delta', (None, 0, 100), ['parent'])])
 
1989
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
1990
            [('tip', 'line-delta,no-eol', (None, 0, 100), ['parent'])])
 
1991
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
1992
            [('tip', 'fulltext', (None, 0, 100), ['parent'])])
 
1993
        # position/length
 
1994
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
1995
            [('tip', 'fulltext,no-eol', (None, 50, 100), ['parent'])])
 
1996
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
1997
            [('tip', 'fulltext,no-eol', (None, 0, 1000), ['parent'])])
 
1998
        # parents
 
1999
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2000
            [('tip', 'fulltext,no-eol', (None, 0, 100), [])])
 
2001
        # change options in the second record
 
2002
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2003
            [('tip', 'fulltext,no-eol', (None, 0, 100), ['parent']),
 
2004
             ('tip', 'no-eol,line-delta', (None, 0, 100), ['parent'])])
 
2005
        self.assertEqual([], self.caught_entries)
 
2006
 
 
2007
    def test_iter_parents(self):
 
2008
        index1 = self.make_g_index('1', 1, [
 
2009
        # no parents
 
2010
            (('r0', ), 'N0 100', ([], )),
 
2011
        # 1 parent
 
2012
            (('r1', ), '', ([('r0', )], ))])
 
2013
        index2 = self.make_g_index('2', 1, [
 
2014
        # 2 parents
 
2015
            (('r2', ), 'N0 100', ([('r1', ), ('r0', )], )),
 
2016
            ])
 
2017
        combined_index = CombinedGraphIndex([index1, index2])
 
2018
        index = KnitGraphIndex(combined_index)
 
2019
        # XXX TODO a ghost
 
2020
        # cases: each sample data individually:
 
2021
        self.assertEqual(set([('r0', ())]),
 
2022
            set(index.iter_parents(['r0'])))
 
2023
        self.assertEqual(set([('r1', ('r0', ))]),
 
2024
            set(index.iter_parents(['r1'])))
 
2025
        self.assertEqual(set([('r2', ('r1', 'r0'))]),
 
2026
            set(index.iter_parents(['r2'])))
 
2027
        # no nodes returned for a missing node
 
2028
        self.assertEqual(set(),
 
2029
            set(index.iter_parents(['missing'])))
 
2030
        # 1 node returned with missing nodes skipped
 
2031
        self.assertEqual(set([('r1', ('r0', ))]),
 
2032
            set(index.iter_parents(['ghost1', 'r1', 'ghost'])))
 
2033
        # 2 nodes returned
 
2034
        self.assertEqual(set([('r0', ()), ('r1', ('r0', ))]),
 
2035
            set(index.iter_parents(['r0', 'r1'])))
 
2036
        # 2 nodes returned, missing skipped
 
2037
        self.assertEqual(set([('r0', ()), ('r1', ('r0', ))]),
 
2038
            set(index.iter_parents(['a', 'r0', 'b', 'r1', 'c'])))
 
2039
 
 
2040
 
 
2041
class TestNoParentsGraphIndexKnit(KnitTests):
 
2042
    """Tests for knits using KnitGraphIndex with no parents."""
 
2043
 
 
2044
    def make_g_index(self, name, ref_lists=0, nodes=[]):
 
2045
        builder = GraphIndexBuilder(ref_lists)
 
2046
        for node, references in nodes:
 
2047
            builder.add_node(node, references)
 
2048
        stream = builder.finish()
 
2049
        trans = self.get_transport()
 
2050
        trans.put_file(name, stream)
 
2051
        return GraphIndex(trans, name)
 
2052
 
 
2053
    def test_parents_deltas_incompatible(self):
 
2054
        index = CombinedGraphIndex([])
 
2055
        self.assertRaises(errors.KnitError, KnitGraphIndex, index,
 
2056
            deltas=True, parents=False)
 
2057
 
 
2058
    def two_graph_index(self, catch_adds=False):
 
2059
        """Build a two-graph index.
 
2060
 
 
2061
        :param deltas: If true, use underlying indices with two node-ref
 
2062
            lists and 'parent' set to a delta-compressed against tail.
 
2063
        """
 
2064
        # put several versions in the index.
 
2065
        index1 = self.make_g_index('1', 0, [
 
2066
            (('tip', ), 'N0 100'),
 
2067
            (('tail', ), '')])
 
2068
        index2 = self.make_g_index('2', 0, [
 
2069
            (('parent', ), ' 100 78'),
 
2070
            (('separate', ), '')])
 
2071
        combined_index = CombinedGraphIndex([index1, index2])
 
2072
        if catch_adds:
 
2073
            self.combined_index = combined_index
 
2074
            self.caught_entries = []
 
2075
            add_callback = self.catch_add
 
2076
        else:
 
2077
            add_callback = None
 
2078
        return KnitGraphIndex(combined_index, parents=False,
 
2079
            add_callback=add_callback)
 
2080
 
 
2081
    def test_get_graph(self):
 
2082
        index = self.two_graph_index()
 
2083
        self.assertEqual(set([
 
2084
            ('tip', ()),
 
2085
            ('tail', ()),
 
2086
            ('parent', ()),
 
2087
            ('separate', ()),
 
2088
            ]), set(index.get_graph()))
 
2089
 
 
2090
    def test_get_ancestry(self):
 
2091
        # with no parents, ancestry is always just the key.
 
2092
        index = self.two_graph_index()
 
2093
        self.assertEqual([], index.get_ancestry([]))
 
2094
        self.assertEqual(['separate'], index.get_ancestry(['separate']))
 
2095
        self.assertEqual(['tail'], index.get_ancestry(['tail']))
 
2096
        self.assertEqual(['parent'], index.get_ancestry(['parent']))
 
2097
        self.assertEqual(['tip'], index.get_ancestry(['tip']))
 
2098
        self.assertTrue(index.get_ancestry(['tip', 'separate']) in
 
2099
            (['tip', 'separate'],
 
2100
             ['separate', 'tip'],
 
2101
            ))
 
2102
        # asking for a ghost makes it go boom.
 
2103
        self.assertRaises(errors.RevisionNotPresent, index.get_ancestry, ['ghost'])
 
2104
 
 
2105
    def test_get_ancestry_with_ghosts(self):
 
2106
        index = self.two_graph_index()
 
2107
        self.assertEqual([], index.get_ancestry_with_ghosts([]))
 
2108
        self.assertEqual(['separate'], index.get_ancestry_with_ghosts(['separate']))
 
2109
        self.assertEqual(['tail'], index.get_ancestry_with_ghosts(['tail']))
 
2110
        self.assertEqual(['parent'], index.get_ancestry_with_ghosts(['parent']))
 
2111
        self.assertEqual(['tip'], index.get_ancestry_with_ghosts(['tip']))
 
2112
        self.assertTrue(index.get_ancestry_with_ghosts(['tip', 'separate']) in
 
2113
            (['tip', 'separate'],
 
2114
             ['separate', 'tip'],
 
2115
            ))
 
2116
        # asking for a ghost makes it go boom.
 
2117
        self.assertRaises(errors.RevisionNotPresent, index.get_ancestry_with_ghosts, ['ghost'])
 
2118
 
 
2119
    def test_num_versions(self):
 
2120
        index = self.two_graph_index()
 
2121
        self.assertEqual(4, index.num_versions())
 
2122
 
 
2123
    def test_get_versions(self):
 
2124
        index = self.two_graph_index()
 
2125
        self.assertEqual(set(['tail', 'tip', 'parent', 'separate']),
 
2126
            set(index.get_versions()))
 
2127
 
 
2128
    def test_has_version(self):
 
2129
        index = self.two_graph_index()
 
2130
        self.assertTrue(index.has_version('tail'))
 
2131
        self.assertFalse(index.has_version('ghost'))
 
2132
 
 
2133
    def test_get_position(self):
 
2134
        index = self.two_graph_index()
 
2135
        self.assertEqual((index._graph_index._indices[0], 0, 100), index.get_position('tip'))
 
2136
        self.assertEqual((index._graph_index._indices[1], 100, 78), index.get_position('parent'))
 
2137
 
 
2138
    def test_get_method(self):
 
2139
        index = self.two_graph_index()
 
2140
        self.assertEqual('fulltext', index.get_method('tip'))
 
2141
        self.assertEqual(['fulltext'], index.get_options('parent'))
 
2142
 
 
2143
    def test_get_options(self):
 
2144
        index = self.two_graph_index()
 
2145
        self.assertEqual(['fulltext', 'no-eol'], index.get_options('tip'))
 
2146
        self.assertEqual(['fulltext'], index.get_options('parent'))
 
2147
 
 
2148
    def test_get_parents(self):
 
2149
        index = self.two_graph_index()
 
2150
        self.assertEqual((), index.get_parents('parent'))
 
2151
        # and errors on ghosts.
 
2152
        self.assertRaises(errors.RevisionNotPresent,
 
2153
            index.get_parents, 'ghost')
 
2154
 
 
2155
    def test_get_parents_with_ghosts(self):
 
2156
        index = self.two_graph_index()
 
2157
        self.assertEqual((), index.get_parents_with_ghosts('parent'))
 
2158
        # and errors on ghosts.
 
2159
        self.assertRaises(errors.RevisionNotPresent,
 
2160
            index.get_parents_with_ghosts, 'ghost')
 
2161
 
 
2162
    def test_check_versions_present(self):
 
2163
        index = self.two_graph_index()
 
2164
        self.assertRaises(RevisionNotPresent, index.check_versions_present,
 
2165
            ['missing'])
 
2166
        self.assertRaises(RevisionNotPresent, index.check_versions_present,
 
2167
            ['tail', 'missing'])
 
2168
        index.check_versions_present(['tail', 'separate'])
 
2169
 
 
2170
    def catch_add(self, entries):
 
2171
        self.caught_entries.append(entries)
 
2172
 
 
2173
    def test_add_no_callback_errors(self):
 
2174
        index = self.two_graph_index()
 
2175
        self.assertRaises(errors.ReadOnlyError, index.add_version,
 
2176
            'new', 'fulltext,no-eol', (None, 50, 60), ['separate'])
 
2177
 
 
2178
    def test_add_version_smoke(self):
 
2179
        index = self.two_graph_index(catch_adds=True)
 
2180
        index.add_version('new', 'fulltext,no-eol', (None, 50, 60), [])
 
2181
        self.assertEqual([[(('new', ), 'N50 60')]],
 
2182
            self.caught_entries)
 
2183
 
 
2184
    def test_add_version_delta_not_delta_index(self):
 
2185
        index = self.two_graph_index(catch_adds=True)
 
2186
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
2187
            'new', 'no-eol,line-delta', (None, 0, 100), [])
 
2188
        self.assertEqual([], self.caught_entries)
 
2189
 
 
2190
    def test_add_version_same_dup(self):
 
2191
        index = self.two_graph_index(catch_adds=True)
 
2192
        # options can be spelt two different ways
 
2193
        index.add_version('tip', 'fulltext,no-eol', (None, 0, 100), [])
 
2194
        index.add_version('tip', 'no-eol,fulltext', (None, 0, 100), [])
 
2195
        # but neither should have added data.
 
2196
        self.assertEqual([[], []], self.caught_entries)
 
2197
        
 
2198
    def test_add_version_different_dup(self):
 
2199
        index = self.two_graph_index(catch_adds=True)
 
2200
        # change options
 
2201
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
2202
            'tip', 'no-eol,line-delta', (None, 0, 100), [])
 
2203
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
2204
            'tip', 'line-delta,no-eol', (None, 0, 100), [])
 
2205
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
2206
            'tip', 'fulltext', (None, 0, 100), [])
 
2207
        # position/length
 
2208
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
2209
            'tip', 'fulltext,no-eol', (None, 50, 100), [])
 
2210
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
2211
            'tip', 'fulltext,no-eol', (None, 0, 1000), [])
 
2212
        # parents
 
2213
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
2214
            'tip', 'fulltext,no-eol', (None, 0, 100), ['parent'])
 
2215
        self.assertEqual([], self.caught_entries)
 
2216
        
 
2217
    def test_add_versions(self):
 
2218
        index = self.two_graph_index(catch_adds=True)
 
2219
        index.add_versions([
 
2220
                ('new', 'fulltext,no-eol', (None, 50, 60), []),
 
2221
                ('new2', 'fulltext', (None, 0, 6), []),
 
2222
                ])
 
2223
        self.assertEqual([(('new', ), 'N50 60'), (('new2', ), ' 0 6')],
 
2224
            sorted(self.caught_entries[0]))
 
2225
        self.assertEqual(1, len(self.caught_entries))
 
2226
 
 
2227
    def test_add_versions_delta_not_delta_index(self):
 
2228
        index = self.two_graph_index(catch_adds=True)
 
2229
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2230
            [('new', 'no-eol,line-delta', (None, 0, 100), ['parent'])])
 
2231
        self.assertEqual([], self.caught_entries)
 
2232
 
 
2233
    def test_add_versions_parents_not_parents_index(self):
 
2234
        index = self.two_graph_index(catch_adds=True)
 
2235
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2236
            [('new', 'no-eol,fulltext', (None, 0, 100), ['parent'])])
 
2237
        self.assertEqual([], self.caught_entries)
 
2238
 
 
2239
    def test_add_versions_same_dup(self):
 
2240
        index = self.two_graph_index(catch_adds=True)
 
2241
        # options can be spelt two different ways
 
2242
        index.add_versions([('tip', 'fulltext,no-eol', (None, 0, 100), [])])
 
2243
        index.add_versions([('tip', 'no-eol,fulltext', (None, 0, 100), [])])
 
2244
        # but neither should have added data.
 
2245
        self.assertEqual([[], []], self.caught_entries)
 
2246
        
 
2247
    def test_add_versions_different_dup(self):
 
2248
        index = self.two_graph_index(catch_adds=True)
 
2249
        # change options
 
2250
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2251
            [('tip', 'no-eol,line-delta', (None, 0, 100), [])])
 
2252
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2253
            [('tip', 'line-delta,no-eol', (None, 0, 100), [])])
 
2254
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2255
            [('tip', 'fulltext', (None, 0, 100), [])])
 
2256
        # position/length
 
2257
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2258
            [('tip', 'fulltext,no-eol', (None, 50, 100), [])])
 
2259
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2260
            [('tip', 'fulltext,no-eol', (None, 0, 1000), [])])
 
2261
        # parents
 
2262
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2263
            [('tip', 'fulltext,no-eol', (None, 0, 100), ['parent'])])
 
2264
        # change options in the second record
 
2265
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2266
            [('tip', 'fulltext,no-eol', (None, 0, 100), []),
 
2267
             ('tip', 'no-eol,line-delta', (None, 0, 100), [])])
 
2268
        self.assertEqual([], self.caught_entries)
 
2269
 
 
2270
    def test_iter_parents(self):
 
2271
        index = self.two_graph_index()
 
2272
        self.assertEqual(set([
 
2273
            ('tip', ()), ('tail', ()), ('parent', ()), ('separate', ())
 
2274
            ]),
 
2275
            set(index.iter_parents(['tip', 'tail', 'ghost', 'parent', 'separate'])))
 
2276
        self.assertEqual(set([('tip', ())]),
 
2277
            set(index.iter_parents(['tip'])))
 
2278
        self.assertEqual(set(),
 
2279
            set(index.iter_parents([])))