17
17
"""Tests for Knit data structure"""
19
from cStringIO import StringIO
33
from bzrlib.errors import (
34
RevisionAlreadyPresent,
39
from bzrlib.index import *
40
from bzrlib.knit import (
33
from ..bzr.index import *
34
from ..bzr.knit import (
41
35
AnnotatedKnitContent,
38
KnitDataStreamIncompatible,
39
KnitDataStreamUnknown,
41
KnitIndexUnknownMethod,
44
42
KnitVersionedFiles,
46
44
_VFContentMapGenerator,
53
from bzrlib.repofmt import pack_repo
54
from bzrlib.tests import (
50
from ..patiencediff import PatienceSequenceMatcher
55
from ..sixish import (
58
60
TestCaseWithMemoryTransport,
59
61
TestCaseWithTransport,
62
from bzrlib.transport import get_transport
63
from bzrlib.transport.memory import MemoryTransport
64
from bzrlib.tuned_gzip import GzipFile
65
from bzrlib.versionedfile import (
64
from ..bzr.versionedfile import (
66
65
AbsentContentFactory,
68
67
network_bytes_to_kind_and_offset,
69
68
RecordingVersionedFilesDecorator,
73
compiled_knit_feature = tests.ModuleAvailableFeature(
74
'bzrlib._knit_load_data_pyx')
75
compiled_knit_feature = features.ModuleAvailableFeature(
76
'breezy.bzr._knit_load_data_pyx')
79
class ErrorTests(TestCase):
81
def test_knit_data_stream_incompatible(self):
82
error = KnitDataStreamIncompatible(
83
'stream format', 'target format')
84
self.assertEqual('Cannot insert knit data stream of format '
85
'"stream format" into knit of format '
86
'"target format".', str(error))
88
def test_knit_data_stream_unknown(self):
89
error = KnitDataStreamUnknown(
91
self.assertEqual('Cannot parse knit data stream of format '
92
'"stream format".', str(error))
94
def test_knit_header_error(self):
95
error = KnitHeaderError('line foo\n', 'path/to/file')
96
self.assertEqual("Knit header error: 'line foo\\n' unexpected"
97
" for file \"path/to/file\".", str(error))
99
def test_knit_index_unknown_method(self):
100
error = KnitIndexUnknownMethod('http://host/foo.kndx',
102
self.assertEqual("Knit index http://host/foo.kndx does not have a"
103
" known method in options: ['bad', 'no-eol']",
77
107
class KnitContentTestsMixin(object):
106
136
line_delta = source_content.line_delta(target_content)
107
137
delta_blocks = list(KnitContent.get_line_delta_blocks(line_delta,
108
138
source_lines, target_lines))
109
matcher = KnitSequenceMatcher(None, source_lines, target_lines)
110
matcher_blocks = list(list(matcher.get_matching_blocks()))
139
matcher = PatienceSequenceMatcher(None, source_lines, target_lines)
140
matcher_blocks = list(matcher.get_matching_blocks())
111
141
self.assertEqual(matcher_blocks, delta_blocks)
113
143
def test_get_line_delta_blocks(self):
206
236
content1 = self._make_content([("", "a"), ("", "b")])
207
237
content2 = self._make_content([("", "a"), ("", "a"), ("", "c")])
208
238
it = content1.line_delta_iter(content2)
209
self.assertEqual(it.next(), (1, 2, 2, ["a", "c"]))
210
self.assertRaises(StopIteration, it.next)
239
self.assertEqual(next(it), (1, 2, 2, ["a", "c"]))
240
self.assertRaises(StopIteration, next, it)
213
243
class TestAnnotatedKnitContent(TestCase, KnitContentTestsMixin):
233
263
content1 = self._make_content([("", "a"), ("", "b")])
234
264
content2 = self._make_content([("", "a"), ("", "a"), ("", "c")])
235
265
it = content1.line_delta_iter(content2)
236
self.assertEqual(it.next(), (1, 2, 2, [("", "a"), ("", "c")]))
237
self.assertRaises(StopIteration, it.next)
266
self.assertEqual(next(it), (1, 2, 2, [("", "a"), ("", "c")]))
267
self.assertRaises(StopIteration, next, it)
240
270
class MockTransport(object):
248
278
def get(self, filename):
249
279
if self.file_lines is None:
250
raise NoSuchFile(filename)
280
raise errors.NoSuchFile(filename)
252
return StringIO("\n".join(self.file_lines))
282
return BytesIO(b"\n".join(self.file_lines))
254
284
def readv(self, relpath, offsets):
255
285
fp = self.get(relpath)
333
363
transport.append_bytes(packname, bytes)
334
364
writer = pack.ContainerWriter(write_data)
336
access = _DirectPackAccess({})
366
access = pack_repo._DirectPackAccess({})
337
367
access.set_writer(writer, index, (transport, packname))
338
368
return access, writer
379
def test_pack_collection_pack_retries(self):
380
"""An explicit pack of a pack collection succeeds even when a
381
concurrent pack happens.
383
builder = self.make_branch_builder('.')
384
builder.start_series()
385
builder.build_snapshot(None, [
386
('add', ('', 'root-id', 'directory', None)),
387
('add', ('file', 'file-id', 'file', 'content\nrev 1\n')),
388
], revision_id='rev-1')
389
builder.build_snapshot(['rev-1'], [
390
('modify', ('file-id', 'content\nrev 2\n')),
391
], revision_id='rev-2')
392
builder.build_snapshot(['rev-2'], [
393
('modify', ('file-id', 'content\nrev 3\n')),
394
], revision_id='rev-3')
395
self.addCleanup(builder.finish_series)
396
b = builder.get_branch()
397
self.addCleanup(b.lock_write().unlock)
399
collection = repo._pack_collection
400
# Concurrently repack the repo.
401
reopened_repo = repo.controldir.open_repository()
349
406
def make_vf_for_retrying(self):
350
407
"""Create 3 packs and a reload function.
359
416
builder = self.make_branch_builder('.', format="1.9")
360
417
builder.start_series()
361
builder.build_snapshot('rev-1', None, [
418
builder.build_snapshot(None, [
362
419
('add', ('', 'root-id', 'directory', None)),
363
420
('add', ('file', 'file-id', 'file', 'content\nrev 1\n')),
365
builder.build_snapshot('rev-2', ['rev-1'], [
421
], revision_id='rev-1')
422
builder.build_snapshot(['rev-1'], [
366
423
('modify', ('file-id', 'content\nrev 2\n')),
368
builder.build_snapshot('rev-3', ['rev-2'], [
424
], revision_id='rev-2')
425
builder.build_snapshot(['rev-2'], [
369
426
('modify', ('file-id', 'content\nrev 3\n')),
427
], revision_id='rev-3')
371
428
builder.finish_series()
372
429
b = builder.get_branch()
378
435
collection = repo._pack_collection
379
436
collection.ensure_loaded()
380
437
orig_packs = collection.packs
381
packer = pack_repo.Packer(collection, orig_packs, '.testpack')
438
packer = knitpack_repo.KnitPacker(collection, orig_packs, '.testpack')
382
439
new_pack = packer.pack()
383
440
# forget about the new pack
384
441
collection.reset()
422
479
raise _TestException('foobar')
423
except _TestException, e:
480
except _TestException as e:
424
481
retry_exc = errors.RetryWithNewPacks(None, reload_occurred=False,
425
482
exc_info=sys.exc_info())
483
# GZ 2010-08-10: Cycle with exc_info affects 3 tests
428
486
def test_read_from_several_packs(self):
437
495
memos.extend(access.add_raw_records([('key', 5)], 'alpha'))
439
497
transport = self.get_transport()
440
access = _DirectPackAccess({"FOO":(transport, 'packfile'),
498
access = pack_repo._DirectPackAccess({"FOO":(transport, 'packfile'),
441
499
"FOOBAR":(transport, 'pack2'),
442
500
"BAZ":(transport, 'pack3')})
443
501
self.assertEqual(['1234567890', '12345', 'alpha'],
471
529
transport = self.get_transport()
472
530
reload_called, reload_func = self.make_reload_func()
473
531
# Note that the index key has changed from 'foo' to 'bar'
474
access = _DirectPackAccess({'bar':(transport, 'packname')},
532
access = pack_repo._DirectPackAccess({'bar':(transport, 'packname')},
475
533
reload_func=reload_func)
476
534
e = self.assertListRaises(errors.RetryWithNewPacks,
477
535
access.get_raw_records, memos)
486
544
memos = self.make_pack_file()
487
545
transport = self.get_transport()
488
546
# Note that the index key has changed from 'foo' to 'bar'
489
access = _DirectPackAccess({'bar':(transport, 'packname')})
547
access = pack_repo._DirectPackAccess({'bar':(transport, 'packname')})
490
548
e = self.assertListRaises(KeyError, access.get_raw_records, memos)
492
550
def test_missing_file_raises_retry(self):
494
552
transport = self.get_transport()
495
553
reload_called, reload_func = self.make_reload_func()
496
554
# Note that the 'filename' has been changed to 'different-packname'
497
access = _DirectPackAccess({'foo':(transport, 'different-packname')},
498
reload_func=reload_func)
555
access = pack_repo._DirectPackAccess(
556
{'foo':(transport, 'different-packname')},
557
reload_func=reload_func)
499
558
e = self.assertListRaises(errors.RetryWithNewPacks,
500
559
access.get_raw_records, memos)
501
560
# The file has gone missing, so we assume we need to reload
509
568
memos = self.make_pack_file()
510
569
transport = self.get_transport()
511
570
# Note that the 'filename' has been changed to 'different-packname'
512
access = _DirectPackAccess({'foo':(transport, 'different-packname')})
571
access = pack_repo._DirectPackAccess(
572
{'foo': (transport, 'different-packname')})
513
573
e = self.assertListRaises(errors.NoSuchFile,
514
574
access.get_raw_records, memos)
519
579
failing_transport = MockReadvFailingTransport(
520
580
[transport.get_bytes('packname')])
521
581
reload_called, reload_func = self.make_reload_func()
522
access = _DirectPackAccess({'foo':(failing_transport, 'packname')},
523
reload_func=reload_func)
582
access = pack_repo._DirectPackAccess(
583
{'foo': (failing_transport, 'packname')},
584
reload_func=reload_func)
524
585
# Asking for a single record will not trigger the Mock failure
525
586
self.assertEqual(['1234567890'],
526
587
list(access.get_raw_records(memos[:1])))
542
603
failing_transport = MockReadvFailingTransport(
543
604
[transport.get_bytes('packname')])
544
605
reload_called, reload_func = self.make_reload_func()
545
access = _DirectPackAccess({'foo':(failing_transport, 'packname')})
606
access = pack_repo._DirectPackAccess(
607
{'foo':(failing_transport, 'packname')})
546
608
# Asking for a single record will not trigger the Mock failure
547
609
self.assertEqual(['1234567890'],
548
610
list(access.get_raw_records(memos[:1])))
553
615
access.get_raw_records, memos)
555
617
def test_reload_or_raise_no_reload(self):
556
access = _DirectPackAccess({}, reload_func=None)
618
access = pack_repo._DirectPackAccess({}, reload_func=None)
557
619
retry_exc = self.make_retry_exception()
558
620
# Without a reload_func, we will just re-raise the original exception
559
621
self.assertRaises(_TestException, access.reload_or_raise, retry_exc)
561
623
def test_reload_or_raise_reload_changed(self):
562
624
reload_called, reload_func = self.make_reload_func(return_val=True)
563
access = _DirectPackAccess({}, reload_func=reload_func)
625
access = pack_repo._DirectPackAccess({}, reload_func=reload_func)
564
626
retry_exc = self.make_retry_exception()
565
627
access.reload_or_raise(retry_exc)
566
628
self.assertEqual([1], reload_called)
571
633
def test_reload_or_raise_reload_no_change(self):
572
634
reload_called, reload_func = self.make_reload_func(return_val=False)
573
access = _DirectPackAccess({}, reload_func=reload_func)
635
access = pack_repo._DirectPackAccess({}, reload_func=reload_func)
574
636
retry_exc = self.make_retry_exception()
575
637
# If reload_occurred is False, then we consider it an error to have
576
638
# reload_func() return False (no changes).
595
657
self.fail('Annotation was not identical with reloading.')
596
658
# Now delete the packs-in-use, which should trigger another reload, but
597
659
# this time we just raise an exception because we can't recover
598
for trans, name in vf._access._indices.itervalues():
660
for trans, name in vf._access._indices.values():
599
661
trans.delete(name)
600
662
self.assertRaises(errors.NoSuchFile, vf.annotate, key)
601
663
self.assertEqual([2, 1, 1], reload_counter)
608
670
self.assertEqual([1, 1, 0], reload_counter)
609
671
# Now delete the packs-in-use, which should trigger another reload, but
610
672
# this time we just raise an exception because we can't recover
611
for trans, name in vf._access._indices.itervalues():
673
for trans, name in vf._access._indices.values():
612
674
trans.delete(name)
613
675
self.assertRaises(errors.NoSuchFile, vf._get_record_map, keys)
614
676
self.assertEqual([2, 1, 1], reload_counter)
617
679
vf, reload_counter = self.make_vf_for_retrying()
618
680
keys = [('rev-1',), ('rev-2',), ('rev-3',)]
619
681
record_stream = vf.get_record_stream(keys, 'topological', False)
620
record = record_stream.next()
682
record = next(record_stream)
621
683
self.assertEqual(('rev-1',), record.key)
622
684
self.assertEqual([0, 0, 0], reload_counter)
623
record = record_stream.next()
685
record = next(record_stream)
624
686
self.assertEqual(('rev-2',), record.key)
625
687
self.assertEqual([1, 1, 0], reload_counter)
626
record = record_stream.next()
688
record = next(record_stream)
627
689
self.assertEqual(('rev-3',), record.key)
628
690
self.assertEqual([1, 1, 0], reload_counter)
629
691
# Now delete all pack files, and see that we raise the right error
630
for trans, name in vf._access._indices.itervalues():
692
for trans, name in vf._access._indices.values():
631
693
trans.delete(name)
632
694
self.assertListRaises(errors.NoSuchFile,
633
695
vf.get_record_stream, keys, 'topological', False)
651
713
self.assertEqual(plain_lines, reload_lines)
652
714
self.assertEqual(21, len(plain_lines))
653
715
# Now delete all pack files, and see that we raise the right error
654
for trans, name in vf._access._indices.itervalues():
716
for trans, name in vf._access._indices.values():
655
717
trans.delete(name)
656
718
self.assertListRaises(errors.NoSuchFile,
657
719
vf.iter_lines_added_or_present_in_keys, keys)
773
835
access = _KnitKeyAccess(transport, ConstantMapper('filename'))
774
836
knit = KnitVersionedFiles(None, access)
775
837
records = [(('rev-id-1',), (('rev-id-1',), 0, len(gz_txt)))]
776
self.assertRaises(errors.KnitCorrupt, list,
838
self.assertRaises(KnitCorrupt, list,
777
839
knit._read_records_iter(records))
779
841
# read_records_iter_raw won't detect that sort of mismatch/corruption
781
843
self.assertEqual([(('rev-id-1',), gz_txt, sha1sum)], raw_contents)
783
845
def test_too_many_lines(self):
784
sha1sum = osutils.sha('foo\nbar\n').hexdigest()
846
sha1sum = osutils.sha_string('foo\nbar\n')
785
847
# record says 1 lines data says 2
786
848
gz_txt = self.create_gz_content('version rev-id-1 1 %s\n'
792
854
access = _KnitKeyAccess(transport, ConstantMapper('filename'))
793
855
knit = KnitVersionedFiles(None, access)
794
856
records = [(('rev-id-1',), (('rev-id-1',), 0, len(gz_txt)))]
795
self.assertRaises(errors.KnitCorrupt, list,
857
self.assertRaises(KnitCorrupt, list,
796
858
knit._read_records_iter(records))
798
860
# read_records_iter_raw won't detect that sort of mismatch/corruption
800
862
self.assertEqual([(('rev-id-1',), gz_txt, sha1sum)], raw_contents)
802
864
def test_mismatched_version_id(self):
803
sha1sum = osutils.sha('foo\nbar\n').hexdigest()
865
sha1sum = osutils.sha_string('foo\nbar\n')
804
866
gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
811
873
knit = KnitVersionedFiles(None, access)
812
874
# We are asking for rev-id-2, but the data is rev-id-1
813
875
records = [(('rev-id-2',), (('rev-id-2',), 0, len(gz_txt)))]
814
self.assertRaises(errors.KnitCorrupt, list,
876
self.assertRaises(KnitCorrupt, list,
815
877
knit._read_records_iter(records))
817
879
# read_records_iter_raw detects mismatches in the header
818
self.assertRaises(errors.KnitCorrupt, list,
880
self.assertRaises(KnitCorrupt, list,
819
881
knit._read_records_iter_raw(records))
821
883
def test_uncompressed_data(self):
822
sha1sum = osutils.sha('foo\nbar\n').hexdigest()
884
sha1sum = osutils.sha_string('foo\nbar\n')
823
885
txt = ('version rev-id-1 2 %s\n'
831
893
records = [(('rev-id-1',), (('rev-id-1',), 0, len(txt)))]
833
895
# We don't have valid gzip data ==> corrupt
834
self.assertRaises(errors.KnitCorrupt, list,
896
self.assertRaises(KnitCorrupt, list,
835
897
knit._read_records_iter(records))
837
899
# read_records_iter_raw will notice the bad data
838
self.assertRaises(errors.KnitCorrupt, list,
900
self.assertRaises(KnitCorrupt, list,
839
901
knit._read_records_iter_raw(records))
841
903
def test_corrupted_data(self):
842
sha1sum = osutils.sha('foo\nbar\n').hexdigest()
904
sha1sum = osutils.sha_string('foo\nbar\n')
843
905
gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
851
913
access = _KnitKeyAccess(transport, ConstantMapper('filename'))
852
914
knit = KnitVersionedFiles(None, access)
853
915
records = [(('rev-id-1',), (('rev-id-1',), 0, len(gz_txt)))]
854
self.assertRaises(errors.KnitCorrupt, list,
916
self.assertRaises(KnitCorrupt, list,
855
917
knit._read_records_iter(records))
856
918
# read_records_iter_raw will barf on bad gz data
857
self.assertRaises(errors.KnitCorrupt, list,
919
self.assertRaises(KnitCorrupt, list,
858
920
knit._read_records_iter_raw(records))
863
925
def get_knit_index(self, transport, name, mode):
864
926
mapper = ConstantMapper(name)
865
from bzrlib._knit_load_data_py import _load_data_py
927
from ..bzr._knit_load_data_py import _load_data_py
866
928
self.overrideAttr(knit, '_load_data', _load_data_py)
867
929
allow_writes = lambda: 'w' in mode
868
930
return _KndxIndex(transport, mapper, lambda:None, allow_writes, lambda:True)
872
934
index = self.get_knit_index(transport, "filename", "w")
874
936
call = transport.calls.pop(0)
875
# call[1][1] is a StringIO - we can't test it by simple equality.
937
# call[1][1] is a BytesIO - we can't test it by simple equality.
876
938
self.assertEqual('put_file_non_atomic', call[0])
877
939
self.assertEqual('filename.kndx', call[1][0])
878
940
# With no history, _KndxIndex writes a new index:
915
977
index = self.get_knit_index(transport, "filename", "r")
916
978
self.assertEqual(1, len(index.keys()))
917
self.assertEqual(set([("version",)]), index.keys())
979
self.assertEqual({("version",)}, index.keys())
919
981
def test_read_corrupted_header(self):
920
982
transport = MockTransport(['not a bzr knit index header\n'])
960
1022
index.add_records([
961
1023
((utf8_revision_id,), ["option"], ((utf8_revision_id,), 0, 1), [])])
962
1024
call = transport.calls.pop(0)
963
# call[1][1] is a StringIO - we can't test it by simple equality.
1025
# call[1][1] is a BytesIO - we can't test it by simple equality.
964
1026
self.assertEqual('put_file_non_atomic', call[0])
965
1027
self.assertEqual('filename.kndx', call[1][0])
966
1028
# With no history, _KndxIndex writes a new index:
979
1041
index.add_records([
980
1042
(("version",), ["option"], (("version",), 0, 1), [(utf8_revision_id,)])])
981
1043
call = transport.calls.pop(0)
982
# call[1][1] is a StringIO - we can't test it by simple equality.
1044
# call[1][1] is a BytesIO - we can't test it by simple equality.
983
1045
self.assertEqual('put_file_non_atomic', call[0])
984
1046
self.assertEqual('filename.kndx', call[1][0])
985
1047
# With no history, _KndxIndex writes a new index:
997
1059
self.assertEqual(set(), index.keys())
999
1061
index.add_records([(("a",), ["option"], (("a",), 0, 1), [])])
1000
self.assertEqual(set([("a",)]), index.keys())
1062
self.assertEqual({("a",)}, index.keys())
1002
1064
index.add_records([(("a",), ["option"], (("a",), 0, 1), [])])
1003
self.assertEqual(set([("a",)]), index.keys())
1065
self.assertEqual({("a",)}, index.keys())
1005
1067
index.add_records([(("b",), ["option"], (("b",), 0, 1), [])])
1006
self.assertEqual(set([("a",), ("b",)]), index.keys())
1068
self.assertEqual({("a",), ("b",)}, index.keys())
1008
1070
def add_a_b(self, index, random_id=None):
1034
1096
self.add_a_b(index)
1035
1097
call = transport.calls.pop(0)
1036
# call[1][1] is a StringIO - we can't test it by simple equality.
1098
# call[1][1] is a BytesIO - we can't test it by simple equality.
1037
1099
self.assertEqual('put_file_non_atomic', call[0])
1038
1100
self.assertEqual('filename.kndx', call[1][0])
1039
1101
# With no history, _KndxIndex writes a new index:
1073
1135
self.assertEqual(_KndxIndex.HEADER, call[1][1].getvalue())
1074
1136
self.assertEqual({'create_parent_dir': True}, call[2])
1075
1137
call = transport.calls.pop(0)
1076
# call[1][1] is a StringIO - we can't test it by simple equality.
1138
# call[1][1] is a BytesIO - we can't test it by simple equality.
1077
1139
self.assertEqual('put_file_non_atomic', call[0])
1078
1140
self.assertEqual('filename.kndx', call[1][0])
1079
1141
# With no history, _KndxIndex writes a new index:
1128
1190
self.assertEqual("fulltext", index.get_method("a"))
1129
1191
self.assertEqual("line-delta", index.get_method("b"))
1130
self.assertRaises(errors.KnitIndexUnknownMethod, index.get_method, "c")
1192
self.assertRaises(knit.KnitIndexUnknownMethod, index.get_method, "c")
1132
1194
def test_get_options(self):
1133
1195
transport = MockTransport([
1150
1212
index = self.get_knit_index(transport, "filename", "r")
1152
1214
self.assertEqual({
1154
("b",):(("a",), ("c",)),
1155
("c",):(("b",), ("a",), ("e",)),
1216
("b",): (("a",), ("c",)),
1217
("c",): (("b",), ("a",), ("e",)),
1156
1218
}, index.get_parent_map(index.keys()))
1158
1220
def test_impossible_parent(self):
1163
1225
"b option 0 1 4 :" # We don't have a 4th record
1165
1227
index = self.get_knit_index(transport, 'filename', 'r')
1167
self.assertRaises(errors.KnitCorrupt, index.keys)
1168
except TypeError, e:
1169
if (str(e) == ('exceptions must be strings, classes, or instances,'
1170
' not exceptions.IndexError')
1171
and sys.version_info[0:2] >= (2,5)):
1172
self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
1173
' raising new style exceptions with python'
1228
self.assertRaises(KnitCorrupt, index.keys)
1178
1230
def test_corrupted_parent(self):
1179
1231
transport = MockTransport([
1183
1235
"c option 0 1 1v :", # Can't have a parent of '1v'
1185
1237
index = self.get_knit_index(transport, 'filename', 'r')
1187
self.assertRaises(errors.KnitCorrupt, index.keys)
1188
except TypeError, e:
1189
if (str(e) == ('exceptions must be strings, classes, or instances,'
1190
' not exceptions.ValueError')
1191
and sys.version_info[0:2] >= (2,5)):
1192
self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
1193
' raising new style exceptions with python'
1238
self.assertRaises(KnitCorrupt, index.keys)
1198
1240
def test_corrupted_parent_in_list(self):
1199
1241
transport = MockTransport([
1203
1245
"c option 0 1 1 v :", # Can't have a parent of 'v'
1205
1247
index = self.get_knit_index(transport, 'filename', 'r')
1207
self.assertRaises(errors.KnitCorrupt, index.keys)
1208
except TypeError, e:
1209
if (str(e) == ('exceptions must be strings, classes, or instances,'
1210
' not exceptions.ValueError')
1211
and sys.version_info[0:2] >= (2,5)):
1212
self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
1213
' raising new style exceptions with python'
1248
self.assertRaises(KnitCorrupt, index.keys)
1218
1250
def test_invalid_position(self):
1219
1251
transport = MockTransport([
1221
1253
"a option 1v 1 :",
1223
1255
index = self.get_knit_index(transport, 'filename', 'r')
1225
self.assertRaises(errors.KnitCorrupt, index.keys)
1226
except TypeError, e:
1227
if (str(e) == ('exceptions must be strings, classes, or instances,'
1228
' not exceptions.ValueError')
1229
and sys.version_info[0:2] >= (2,5)):
1230
self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
1231
' raising new style exceptions with python'
1256
self.assertRaises(KnitCorrupt, index.keys)
1236
1258
def test_invalid_size(self):
1237
1259
transport = MockTransport([
1239
1261
"a option 1 1v :",
1241
1263
index = self.get_knit_index(transport, 'filename', 'r')
1243
self.assertRaises(errors.KnitCorrupt, index.keys)
1244
except TypeError, e:
1245
if (str(e) == ('exceptions must be strings, classes, or instances,'
1246
' not exceptions.ValueError')
1247
and sys.version_info[0:2] >= (2,5)):
1248
self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
1249
' raising new style exceptions with python'
1264
self.assertRaises(KnitCorrupt, index.keys)
1254
1266
def test_scan_unvalidated_index_not_implemented(self):
1255
1267
transport = MockTransport()
1267
1279
"b option 10 10 0", # This line isn't terminated, ignored
1269
1281
index = self.get_knit_index(transport, "filename", "r")
1270
self.assertEqual(set([('a',)]), index.keys())
1282
self.assertEqual({('a',)}, index.keys())
1272
1284
def test_skip_incomplete_record(self):
1273
1285
# A line with bogus data should just be skipped
1278
1290
"c option 20 10 0 :", # Properly terminated, and starts with '\n'
1280
1292
index = self.get_knit_index(transport, "filename", "r")
1281
self.assertEqual(set([('a',), ('c',)]), index.keys())
1293
self.assertEqual({('a',), ('c',)}, index.keys())
1283
1295
def test_trailing_characters(self):
1284
1296
# A line with bogus data should just be skipped
1289
1301
"c option 20 10 0 :", # Properly terminated, and starts with '\n'
1291
1303
index = self.get_knit_index(transport, "filename", "r")
1292
self.assertEqual(set([('a',), ('c',)]), index.keys())
1304
self.assertEqual({('a',), ('c',)}, index.keys())
1295
1307
class LowLevelKnitIndexTests_c(LowLevelKnitIndexTests):
1299
1311
def get_knit_index(self, transport, name, mode):
1300
1312
mapper = ConstantMapper(name)
1301
from bzrlib._knit_load_data_pyx import _load_data_c
1313
from ..bzr._knit_load_data_pyx import _load_data_c
1302
1314
self.overrideAttr(knit, '_load_data', _load_data_c)
1303
1315
allow_writes = lambda: mode == 'w'
1304
1316
return _KndxIndex(transport, mapper, lambda:None,
1409
1421
details = ('line-delta', False)
1410
1422
p1_record = ['line1\n', 'line2\n']
1411
1423
ann._num_compression_children[p1_key] = 1
1412
res = ann._expand_record(rev_key, (p1_key,p2_key), p1_key,
1424
res = ann._expand_record(rev_key, (p1_key, p2_key), p1_key,
1413
1425
record, details)
1414
1426
self.assertEqual(None, res)
1415
1427
# self.assertTrue(p1_key in ann._pending_deltas)
1494
1506
target.add_lines(basis, (), ['gam\n'])
1495
1507
target.insert_record_stream(
1496
1508
source.get_record_stream([broken], 'unordered', False))
1497
err = self.assertRaises(errors.KnitCorrupt,
1509
err = self.assertRaises(KnitCorrupt,
1498
1510
target.get_record_stream([broken], 'unordered', True
1499
1511
).next().get_bytes_as, 'chunked')
1500
1512
self.assertEqual(['gam\n', 'bar\n'], err.content)
1525
1537
'a-2 fulltext 0 0 0 :\n'
1526
1538
'a-3 fulltext 0 0 1 :'
1528
self.assertEqual(set([('a-3',), ('a-1',), ('a-2',)]), idx.keys())
1540
self.assertEqual({('a-3',), ('a-1',), ('a-2',)}, idx.keys())
1529
1541
self.assertEqual({
1530
1542
('a-1',): ((('a-1',), 0, 0), None, (), ('fulltext', False)),
1531
1543
('a-2',): ((('a-2',), 0, 0), None, (('a-1',),), ('fulltext', False)),
1532
1544
('a-3',): ((('a-3',), 0, 0), None, (('a-2',),), ('fulltext', False)),
1533
1545
}, idx.get_build_details(idx.keys()))
1534
self.assertEqual({('a-1',):(),
1535
('a-2',):(('a-1',),),
1536
('a-3',):(('a-2',),),},
1546
self.assertEqual({('a-1',): (),
1547
('a-2',): (('a-1',),),
1548
('a-3',): (('a-2',),),},
1537
1549
idx.get_parent_map(idx.keys()))
1539
1551
def test_add_versions_fails_clean(self):
1564
1576
# Assert the pre-condition
1565
1577
def assertA1Only():
1566
self.assertEqual(set([('a-1',)]), set(idx.keys()))
1578
self.assertEqual({('a-1',)}, set(idx.keys()))
1567
1579
self.assertEqual(
1568
1580
{('a-1',): ((('a-1',), 0, 0), None, (), ('fulltext', False))},
1569
1581
idx.get_build_details([('a-1',)]))
1579
1591
# could leave an empty .kndx file, which bzr would later claim was a
1580
1592
# corrupted file since the header was not present. In reality, the file
1581
1593
# just wasn't created, so it should be ignored.
1582
t = get_transport('.')
1594
t = transport.get_transport_from_path('.')
1583
1595
t.put_bytes('test.kndx', '')
1585
1597
knit = self.make_test_knit()
1587
1599
def test_knit_index_checks_header(self):
1588
t = get_transport('.')
1600
t = transport.get_transport_from_path('.')
1589
1601
t.put_bytes('test.kndx', '# not really a knit header\n\n')
1590
1602
k = self.make_test_knit()
1591
1603
self.assertRaises(KnitHeaderError, k.keys)
1639
1651
def test_keys(self):
1640
1652
index = self.two_graph_index()
1641
self.assertEqual(set([('tail',), ('tip',), ('parent',), ('separate',)]),
1653
self.assertEqual({('tail',), ('tip',), ('parent',), ('separate',)},
1642
1654
set(index.keys()))
1644
1656
def test_get_position(self):
1691
1703
def test_add_version_delta_not_delta_index(self):
1692
1704
index = self.two_graph_index(catch_adds=True)
1693
self.assertRaises(errors.KnitCorrupt, index.add_records,
1705
self.assertRaises(KnitCorrupt, index.add_records,
1694
1706
[(('new',), 'no-eol,line-delta', (None, 0, 100), [('parent',)])])
1695
1707
self.assertEqual([], self.caught_entries)
1711
1723
def test_add_version_different_dup(self):
1712
1724
index = self.two_graph_index(deltas=True, catch_adds=True)
1713
1725
# change options
1714
self.assertRaises(errors.KnitCorrupt, index.add_records,
1726
self.assertRaises(KnitCorrupt, index.add_records,
1715
1727
[(('tip',), 'line-delta', (None, 0, 100), [('parent',)])])
1716
self.assertRaises(errors.KnitCorrupt, index.add_records,
1728
self.assertRaises(KnitCorrupt, index.add_records,
1717
1729
[(('tip',), 'fulltext', (None, 0, 100), [('parent',)])])
1719
self.assertRaises(errors.KnitCorrupt, index.add_records,
1731
self.assertRaises(KnitCorrupt, index.add_records,
1720
1732
[(('tip',), 'fulltext,no-eol', (None, 0, 100), [])])
1721
1733
self.assertEqual([], self.caught_entries)
1745
1757
def test_add_versions_delta_not_delta_index(self):
1746
1758
index = self.two_graph_index(catch_adds=True)
1747
self.assertRaises(errors.KnitCorrupt, index.add_records,
1759
self.assertRaises(KnitCorrupt, index.add_records,
1748
1760
[(('new',), 'no-eol,line-delta', (None, 0, 100), [('parent',)])])
1749
1761
self.assertEqual([], self.caught_entries)
1771
1783
def test_add_versions_different_dup(self):
1772
1784
index = self.two_graph_index(deltas=True, catch_adds=True)
1773
1785
# change options
1774
self.assertRaises(errors.KnitCorrupt, index.add_records,
1786
self.assertRaises(KnitCorrupt, index.add_records,
1775
1787
[(('tip',), 'line-delta', (None, 0, 100), [('parent',)])])
1776
self.assertRaises(errors.KnitCorrupt, index.add_records,
1788
self.assertRaises(KnitCorrupt, index.add_records,
1777
1789
[(('tip',), 'fulltext', (None, 0, 100), [('parent',)])])
1779
self.assertRaises(errors.KnitCorrupt, index.add_records,
1791
self.assertRaises(KnitCorrupt, index.add_records,
1780
1792
[(('tip',), 'fulltext,no-eol', (None, 0, 100), [])])
1781
1793
# change options in the second record
1782
self.assertRaises(errors.KnitCorrupt, index.add_records,
1794
self.assertRaises(KnitCorrupt, index.add_records,
1783
1795
[(('tip',), 'fulltext,no-eol', (None, 0, 100), [('parent',)]),
1784
1796
(('tip',), 'line-delta', (None, 0, 100), [('parent',)])])
1785
1797
self.assertEqual([], self.caught_entries)
1913
1925
def test_parents_deltas_incompatible(self):
1914
1926
index = CombinedGraphIndex([])
1915
self.assertRaises(errors.KnitError, _KnitGraphIndex, lambda:True,
1927
self.assertRaises(knit.KnitError, _KnitGraphIndex, lambda:True,
1916
1928
index, deltas=True, parents=False)
1918
1930
def two_graph_index(self, catch_adds=False):
1941
1953
def test_keys(self):
1942
1954
index = self.two_graph_index()
1943
self.assertEqual(set([('tail',), ('tip',), ('parent',), ('separate',)]),
1955
self.assertEqual({('tail',), ('tip',), ('parent',), ('separate',)},
1944
1956
set(index.keys()))
1946
1958
def test_get_position(self):
1982
1994
def test_add_version_delta_not_delta_index(self):
1983
1995
index = self.two_graph_index(catch_adds=True)
1984
self.assertRaises(errors.KnitCorrupt, index.add_records,
1996
self.assertRaises(KnitCorrupt, index.add_records,
1985
1997
[(('new',), 'no-eol,line-delta', (None, 0, 100), [])])
1986
1998
self.assertEqual([], self.caught_entries)
2000
2012
def test_add_version_different_dup(self):
2001
2013
index = self.two_graph_index(catch_adds=True)
2002
2014
# change options
2003
self.assertRaises(errors.KnitCorrupt, index.add_records,
2015
self.assertRaises(KnitCorrupt, index.add_records,
2004
2016
[(('tip',), 'no-eol,line-delta', (None, 0, 100), [])])
2005
self.assertRaises(errors.KnitCorrupt, index.add_records,
2017
self.assertRaises(KnitCorrupt, index.add_records,
2006
2018
[(('tip',), 'line-delta,no-eol', (None, 0, 100), [])])
2007
self.assertRaises(errors.KnitCorrupt, index.add_records,
2019
self.assertRaises(KnitCorrupt, index.add_records,
2008
2020
[(('tip',), 'fulltext', (None, 0, 100), [])])
2010
self.assertRaises(errors.KnitCorrupt, index.add_records,
2022
self.assertRaises(KnitCorrupt, index.add_records,
2011
2023
[(('tip',), 'fulltext,no-eol', (None, 0, 100), [('parent',)])])
2012
2024
self.assertEqual([], self.caught_entries)
2024
2036
def test_add_versions_delta_not_delta_index(self):
2025
2037
index = self.two_graph_index(catch_adds=True)
2026
self.assertRaises(errors.KnitCorrupt, index.add_records,
2038
self.assertRaises(KnitCorrupt, index.add_records,
2027
2039
[(('new',), 'no-eol,line-delta', (None, 0, 100), [('parent',)])])
2028
2040
self.assertEqual([], self.caught_entries)
2030
2042
def test_add_versions_parents_not_parents_index(self):
2031
2043
index = self.two_graph_index(catch_adds=True)
2032
self.assertRaises(errors.KnitCorrupt, index.add_records,
2044
self.assertRaises(KnitCorrupt, index.add_records,
2033
2045
[(('new',), 'no-eol,fulltext', (None, 0, 100), [('parent',)])])
2034
2046
self.assertEqual([], self.caught_entries)
2052
2064
def test_add_versions_different_dup(self):
2053
2065
index = self.two_graph_index(catch_adds=True)
2054
2066
# change options
2055
self.assertRaises(errors.KnitCorrupt, index.add_records,
2067
self.assertRaises(KnitCorrupt, index.add_records,
2056
2068
[(('tip',), 'no-eol,line-delta', (None, 0, 100), [])])
2057
self.assertRaises(errors.KnitCorrupt, index.add_records,
2069
self.assertRaises(KnitCorrupt, index.add_records,
2058
2070
[(('tip',), 'line-delta,no-eol', (None, 0, 100), [])])
2059
self.assertRaises(errors.KnitCorrupt, index.add_records,
2071
self.assertRaises(KnitCorrupt, index.add_records,
2060
2072
[(('tip',), 'fulltext', (None, 0, 100), [])])
2062
self.assertRaises(errors.KnitCorrupt, index.add_records,
2074
self.assertRaises(KnitCorrupt, index.add_records,
2063
2075
[(('tip',), 'fulltext,no-eol', (None, 0, 100), [('parent',)])])
2064
2076
# change options in the second record
2065
self.assertRaises(errors.KnitCorrupt, index.add_records,
2077
self.assertRaises(KnitCorrupt, index.add_records,
2066
2078
[(('tip',), 'fulltext,no-eol', (None, 0, 100), []),
2067
2079
(('tip',), 'no-eol,line-delta', (None, 0, 100), [])])
2068
2080
self.assertEqual([], self.caught_entries)
2105
2117
self.assertGroupKeysForIo([([f_a], set())],
2106
2118
[f_a], [], positions)
2107
self.assertGroupKeysForIo([([f_a], set([f_a]))],
2119
self.assertGroupKeysForIo([([f_a], {f_a})],
2108
2120
[f_a], [f_a], positions)
2109
2121
self.assertGroupKeysForIo([([f_a, f_b], set([]))],
2110
2122
[f_a, f_b], [], positions)
2111
self.assertGroupKeysForIo([([f_a, f_b], set([f_b]))],
2123
self.assertGroupKeysForIo([([f_a, f_b], {f_b})],
2112
2124
[f_a, f_b], [f_b], positions)
2113
2125
self.assertGroupKeysForIo([([f_a, f_b, g_a, g_b], set())],
2114
2126
[f_a, g_a, f_b, g_b], [], positions)
2211
2223
self.assertEqual([(key_basis, 'foo\n'), (key_basis, 'bar\n')], details)
2212
2224
# Not optimised to date:
2213
2225
# self.assertEqual([("annotate", key_basis)], basis.calls)
2214
self.assertEqual([('get_parent_map', set([key_basis])),
2215
('get_parent_map', set([key_basis])),
2226
self.assertEqual([('get_parent_map', {key_basis}),
2227
('get_parent_map', {key_basis}),
2216
2228
('get_record_stream', [key_basis], 'topological', True)],
2238
2250
parent_map = test.get_parent_map([key, key_basis, key_missing])
2239
2251
self.assertEqual({key: (),
2240
2252
key_basis: ()}, parent_map)
2241
self.assertEqual([("get_parent_map", set([key_basis, key_missing]))],
2253
self.assertEqual([("get_parent_map", {key_basis, key_missing})],
2244
2256
def test_get_record_stream_unordered_fulltexts(self):
2275
2287
# It's not strictly minimal, but it seems reasonable for now for it to
2276
2288
# ask which fallbacks have which parents.
2277
2289
self.assertEqual([
2278
("get_parent_map", set([key_basis, key_missing])),
2290
("get_parent_map", {key_basis, key_missing}),
2279
2291
("get_record_stream", [key_basis], 'unordered', True)],
2315
record = source.get_record_stream([result[0]], 'unordered',
2327
record = next(source.get_record_stream([result[0]], 'unordered',
2317
2329
self.assertEqual(record.key, result[0])
2318
2330
self.assertEqual(record.sha1, result[1])
2319
2331
# We used to check that the storage kind matched, but actually it
2324
2336
# It's not strictly minimal, but it seems reasonable for now for it to
2325
2337
# ask which fallbacks have which parents.
2326
2338
self.assertEqual([
2327
("get_parent_map", set([key_basis, key_basis_2, key_missing])),
2339
("get_parent_map", {key_basis, key_basis_2, key_missing}),
2328
2340
# topological is requested from the fallback, because that is what
2329
2341
# was requested at the top level.
2330
2342
("get_record_stream", [key_basis_2, key_basis], 'topological', True)],
2362
2374
# It's not strictly minimal, but it seems reasonable for now for it to
2363
2375
# ask which fallbacks have which parents.
2364
2376
self.assertEqual([
2365
("get_parent_map", set([key_basis, key_missing])),
2377
("get_parent_map", {key_basis, key_missing}),
2366
2378
("get_record_stream", [key_basis], 'unordered', False)],
2402
record = source.get_record_stream([result[0]], 'unordered',
2414
record = next(source.get_record_stream([result[0]], 'unordered',
2404
2416
self.assertEqual(record.key, result[0])
2405
2417
self.assertEqual(record.sha1, result[1])
2406
2418
self.assertEqual(record.storage_kind, result[2])
2408
2420
# It's not strictly minimal, but it seems reasonable for now for it to
2409
2421
# ask which fallbacks have which parents.
2410
2422
self.assertEqual([
2411
("get_parent_map", set([key_basis, key_basis_2, key_missing])),
2423
("get_parent_map", {key_basis, key_basis_2, key_missing}),
2412
2424
("get_record_stream", [key_basis_2, key_basis], 'topological', False)],
2419
2431
key_basis = ('bar',)
2420
2432
key_missing = ('missing',)
2421
2433
test.add_lines(key, (), ['foo\n'])
2422
key_sha1sum = osutils.sha('foo\n').hexdigest()
2434
key_sha1sum = osutils.sha_string('foo\n')
2423
2435
sha1s = test.get_sha1s([key])
2424
2436
self.assertEqual({key: key_sha1sum}, sha1s)
2425
2437
self.assertEqual([], basis.calls)
2427
2439
# directly (rather than via text reconstruction) so that remote servers
2428
2440
# etc don't have to answer with full content.
2429
2441
basis.add_lines(key_basis, (), ['foo\n', 'bar\n'])
2430
basis_sha1sum = osutils.sha('foo\nbar\n').hexdigest()
2442
basis_sha1sum = osutils.sha_string('foo\nbar\n')
2431
2443
basis.calls = []
2432
2444
sha1s = test.get_sha1s([key, key_missing, key_basis])
2433
2445
self.assertEqual({key: key_sha1sum,
2434
2446
key_basis: basis_sha1sum}, sha1s)
2435
self.assertEqual([("get_sha1s", set([key_basis, key_missing]))],
2447
self.assertEqual([("get_sha1s", {key_basis, key_missing})],
2438
2450
def test_insert_record_stream(self):
2451
2463
test.insert_record_stream(stream)
2452
2464
# XXX: this does somewhat too many calls in making sure of whether it
2453
2465
# has to recreate the full text.
2454
self.assertEqual([("get_parent_map", set([key_basis])),
2455
('get_parent_map', set([key_basis])),
2466
self.assertEqual([("get_parent_map", {key_basis}),
2467
('get_parent_map', {key_basis}),
2456
2468
('get_record_stream', [key_basis], 'unordered', True)],
2458
2470
self.assertEqual({key_delta:(key_basis,)},
2471
2483
basis.calls = []
2472
2484
lines = list(test.iter_lines_added_or_present_in_keys([key1]))
2473
2485
self.assertEqual([("foo\n", key1)], lines)
2474
self.assertEqual([("iter_lines_added_or_present_in_keys", set([key1]))],
2486
self.assertEqual([("iter_lines_added_or_present_in_keys", {key1})],
2476
2488
# keys in both are not duplicated:
2477
2489
test.add_lines(key2, (), ["bar\n"])
2493
2505
basis.add_lines(key1, (), [])
2494
2506
basis.calls = []
2495
2507
keys = test.keys()
2496
self.assertEqual(set([key1]), set(keys))
2508
self.assertEqual({key1}, set(keys))
2497
2509
self.assertEqual([("keys",)], basis.calls)
2498
2510
# keys in both are not duplicated:
2499
2511
test.add_lines(key2, (), [])
2501
2513
basis.calls = []
2502
2514
keys = test.keys()
2503
2515
self.assertEqual(2, len(keys))
2504
self.assertEqual(set([key1, key2]), set(keys))
2516
self.assertEqual({key1, key2}, set(keys))
2505
2517
self.assertEqual([("keys",)], basis.calls)
2507
2519
def test_add_mpdiffs(self):
2519
2531
diffs = source.make_mpdiffs([key_delta])
2520
2532
test.add_mpdiffs([(key_delta, (key_basis,),
2521
2533
source.get_sha1s([key_delta])[key_delta], diffs[0])])
2522
self.assertEqual([("get_parent_map", set([key_basis])),
2534
self.assertEqual([("get_parent_map", {key_basis}),
2523
2535
('get_record_stream', [key_basis], 'unordered', True),],
2525
2537
self.assertEqual({key_delta:(key_basis,)},
2548
2560
self.assertEqual(3, len(basis.calls))
2549
2561
self.assertEqual([
2550
("get_parent_map", set([key_left, key_right])),
2551
("get_parent_map", set([key_left, key_right])),
2562
("get_parent_map", {key_left, key_right}),
2563
("get_parent_map", {key_left, key_right}),
2553
2565
basis.calls[:-1])
2554
2566
last_call = basis.calls[-1]
2555
2567
self.assertEqual('get_record_stream', last_call[0])
2556
self.assertEqual(set([key_left, key_right]), set(last_call[1]))
2568
self.assertEqual({key_left, key_right}, set(last_call[1]))
2557
2569
self.assertEqual('topological', last_call[2])
2558
2570
self.assertEqual(True, last_call[3])