1
# Copyright (C) 2006-2010 Canonical Ltd
1
# Copyright (C) 2006-2011 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
17
17
"""Tests for Knit data structure"""
19
from cStringIO import StringIO
33
from bzrlib.errors import (
34
RevisionAlreadyPresent,
31
from ..errors import (
39
from bzrlib.index import *
40
from bzrlib.knit import (
41
37
AnnotatedKnitContent,
44
39
KnitVersionedFiles,
46
41
_VFContentMapGenerator,
53
from bzrlib.repofmt import pack_repo
54
from bzrlib.tests import (
47
from ..patiencediff import PatienceSequenceMatcher
48
from ..repofmt import (
52
from ..sixish import (
58
57
TestCaseWithMemoryTransport,
59
58
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 (
61
from ..versionedfile import (
66
62
AbsentContentFactory,
68
64
network_bytes_to_kind_and_offset,
69
65
RecordingVersionedFilesDecorator,
73
compiled_knit_feature = tests.ModuleAvailableFeature(
74
'bzrlib._knit_load_data_pyx')
72
compiled_knit_feature = features.ModuleAvailableFeature(
73
'breezy._knit_load_data_pyx')
77
76
class KnitContentTestsMixin(object):
106
105
line_delta = source_content.line_delta(target_content)
107
106
delta_blocks = list(KnitContent.get_line_delta_blocks(line_delta,
108
107
source_lines, target_lines))
109
matcher = KnitSequenceMatcher(None, source_lines, target_lines)
110
matcher_blocks = list(list(matcher.get_matching_blocks()))
108
matcher = PatienceSequenceMatcher(None, source_lines, target_lines)
109
matcher_blocks = list(matcher.get_matching_blocks())
111
110
self.assertEqual(matcher_blocks, delta_blocks)
113
112
def test_get_line_delta_blocks(self):
206
205
content1 = self._make_content([("", "a"), ("", "b")])
207
206
content2 = self._make_content([("", "a"), ("", "a"), ("", "c")])
208
207
it = content1.line_delta_iter(content2)
209
self.assertEqual(it.next(), (1, 2, 2, ["a", "c"]))
210
self.assertRaises(StopIteration, it.next)
208
self.assertEqual(next(it), (1, 2, 2, ["a", "c"]))
209
self.assertRaises(StopIteration, next, it)
213
212
class TestAnnotatedKnitContent(TestCase, KnitContentTestsMixin):
233
232
content1 = self._make_content([("", "a"), ("", "b")])
234
233
content2 = self._make_content([("", "a"), ("", "a"), ("", "c")])
235
234
it = content1.line_delta_iter(content2)
236
self.assertEqual(it.next(), (1, 2, 2, [("", "a"), ("", "c")]))
237
self.assertRaises(StopIteration, it.next)
235
self.assertEqual(next(it), (1, 2, 2, [("", "a"), ("", "c")]))
236
self.assertRaises(StopIteration, next, it)
240
239
class MockTransport(object):
249
248
if self.file_lines is None:
250
249
raise NoSuchFile(filename)
252
return StringIO("\n".join(self.file_lines))
251
return BytesIO(b"\n".join(self.file_lines))
254
253
def readv(self, relpath, offsets):
255
254
fp = self.get(relpath)
333
332
transport.append_bytes(packname, bytes)
334
333
writer = pack.ContainerWriter(write_data)
336
access = _DirectPackAccess({})
335
access = pack_repo._DirectPackAccess({})
337
336
access.set_writer(writer, index, (transport, packname))
338
337
return access, writer
348
def test_pack_collection_pack_retries(self):
349
"""An explicit pack of a pack collection succeeds even when a
350
concurrent pack happens.
352
builder = self.make_branch_builder('.')
353
builder.start_series()
354
builder.build_snapshot('rev-1', None, [
355
('add', ('', 'root-id', 'directory', None)),
356
('add', ('file', 'file-id', 'file', 'content\nrev 1\n')),
358
builder.build_snapshot('rev-2', ['rev-1'], [
359
('modify', ('file-id', 'content\nrev 2\n')),
361
builder.build_snapshot('rev-3', ['rev-2'], [
362
('modify', ('file-id', 'content\nrev 3\n')),
364
self.addCleanup(builder.finish_series)
365
b = builder.get_branch()
366
self.addCleanup(b.lock_write().unlock)
368
collection = repo._pack_collection
369
# Concurrently repack the repo.
370
reopened_repo = repo.bzrdir.open_repository()
349
375
def make_vf_for_retrying(self):
350
376
"""Create 3 packs and a reload function.
378
404
collection = repo._pack_collection
379
405
collection.ensure_loaded()
380
406
orig_packs = collection.packs
381
packer = pack_repo.Packer(collection, orig_packs, '.testpack')
407
packer = knitpack_repo.KnitPacker(collection, orig_packs, '.testpack')
382
408
new_pack = packer.pack()
383
409
# forget about the new pack
384
410
collection.reset()
422
448
raise _TestException('foobar')
423
except _TestException, e:
449
except _TestException as e:
424
450
retry_exc = errors.RetryWithNewPacks(None, reload_occurred=False,
425
451
exc_info=sys.exc_info())
452
# GZ 2010-08-10: Cycle with exc_info affects 3 tests
428
455
def test_read_from_several_packs(self):
437
464
memos.extend(access.add_raw_records([('key', 5)], 'alpha'))
439
466
transport = self.get_transport()
440
access = _DirectPackAccess({"FOO":(transport, 'packfile'),
467
access = pack_repo._DirectPackAccess({"FOO":(transport, 'packfile'),
441
468
"FOOBAR":(transport, 'pack2'),
442
469
"BAZ":(transport, 'pack3')})
443
470
self.assertEqual(['1234567890', '12345', 'alpha'],
454
481
def test_set_writer(self):
455
482
"""The writer should be settable post construction."""
456
access = _DirectPackAccess({})
483
access = pack_repo._DirectPackAccess({})
457
484
transport = self.get_transport()
458
485
packname = 'packfile'
471
498
transport = self.get_transport()
472
499
reload_called, reload_func = self.make_reload_func()
473
500
# Note that the index key has changed from 'foo' to 'bar'
474
access = _DirectPackAccess({'bar':(transport, 'packname')},
501
access = pack_repo._DirectPackAccess({'bar':(transport, 'packname')},
475
502
reload_func=reload_func)
476
503
e = self.assertListRaises(errors.RetryWithNewPacks,
477
504
access.get_raw_records, memos)
486
513
memos = self.make_pack_file()
487
514
transport = self.get_transport()
488
515
# Note that the index key has changed from 'foo' to 'bar'
489
access = _DirectPackAccess({'bar':(transport, 'packname')})
516
access = pack_repo._DirectPackAccess({'bar':(transport, 'packname')})
490
517
e = self.assertListRaises(KeyError, access.get_raw_records, memos)
492
519
def test_missing_file_raises_retry(self):
494
521
transport = self.get_transport()
495
522
reload_called, reload_func = self.make_reload_func()
496
523
# Note that the 'filename' has been changed to 'different-packname'
497
access = _DirectPackAccess({'foo':(transport, 'different-packname')},
498
reload_func=reload_func)
524
access = pack_repo._DirectPackAccess(
525
{'foo':(transport, 'different-packname')},
526
reload_func=reload_func)
499
527
e = self.assertListRaises(errors.RetryWithNewPacks,
500
528
access.get_raw_records, memos)
501
529
# The file has gone missing, so we assume we need to reload
509
537
memos = self.make_pack_file()
510
538
transport = self.get_transport()
511
539
# Note that the 'filename' has been changed to 'different-packname'
512
access = _DirectPackAccess({'foo':(transport, 'different-packname')})
540
access = pack_repo._DirectPackAccess(
541
{'foo': (transport, 'different-packname')})
513
542
e = self.assertListRaises(errors.NoSuchFile,
514
543
access.get_raw_records, memos)
519
548
failing_transport = MockReadvFailingTransport(
520
549
[transport.get_bytes('packname')])
521
550
reload_called, reload_func = self.make_reload_func()
522
access = _DirectPackAccess({'foo':(failing_transport, 'packname')},
523
reload_func=reload_func)
551
access = pack_repo._DirectPackAccess(
552
{'foo': (failing_transport, 'packname')},
553
reload_func=reload_func)
524
554
# Asking for a single record will not trigger the Mock failure
525
555
self.assertEqual(['1234567890'],
526
556
list(access.get_raw_records(memos[:1])))
542
572
failing_transport = MockReadvFailingTransport(
543
573
[transport.get_bytes('packname')])
544
574
reload_called, reload_func = self.make_reload_func()
545
access = _DirectPackAccess({'foo':(failing_transport, 'packname')})
575
access = pack_repo._DirectPackAccess(
576
{'foo':(failing_transport, 'packname')})
546
577
# Asking for a single record will not trigger the Mock failure
547
578
self.assertEqual(['1234567890'],
548
579
list(access.get_raw_records(memos[:1])))
553
584
access.get_raw_records, memos)
555
586
def test_reload_or_raise_no_reload(self):
556
access = _DirectPackAccess({}, reload_func=None)
587
access = pack_repo._DirectPackAccess({}, reload_func=None)
557
588
retry_exc = self.make_retry_exception()
558
589
# Without a reload_func, we will just re-raise the original exception
559
590
self.assertRaises(_TestException, access.reload_or_raise, retry_exc)
561
592
def test_reload_or_raise_reload_changed(self):
562
593
reload_called, reload_func = self.make_reload_func(return_val=True)
563
access = _DirectPackAccess({}, reload_func=reload_func)
594
access = pack_repo._DirectPackAccess({}, reload_func=reload_func)
564
595
retry_exc = self.make_retry_exception()
565
596
access.reload_or_raise(retry_exc)
566
597
self.assertEqual([1], reload_called)
571
602
def test_reload_or_raise_reload_no_change(self):
572
603
reload_called, reload_func = self.make_reload_func(return_val=False)
573
access = _DirectPackAccess({}, reload_func=reload_func)
604
access = pack_repo._DirectPackAccess({}, reload_func=reload_func)
574
605
retry_exc = self.make_retry_exception()
575
606
# If reload_occurred is False, then we consider it an error to have
576
607
# reload_func() return False (no changes).
595
626
self.fail('Annotation was not identical with reloading.')
596
627
# Now delete the packs-in-use, which should trigger another reload, but
597
628
# this time we just raise an exception because we can't recover
598
for trans, name in vf._access._indices.itervalues():
629
for trans, name in vf._access._indices.values():
599
630
trans.delete(name)
600
631
self.assertRaises(errors.NoSuchFile, vf.annotate, key)
601
632
self.assertEqual([2, 1, 1], reload_counter)
608
639
self.assertEqual([1, 1, 0], reload_counter)
609
640
# Now delete the packs-in-use, which should trigger another reload, but
610
641
# this time we just raise an exception because we can't recover
611
for trans, name in vf._access._indices.itervalues():
642
for trans, name in vf._access._indices.values():
612
643
trans.delete(name)
613
644
self.assertRaises(errors.NoSuchFile, vf._get_record_map, keys)
614
645
self.assertEqual([2, 1, 1], reload_counter)
617
648
vf, reload_counter = self.make_vf_for_retrying()
618
649
keys = [('rev-1',), ('rev-2',), ('rev-3',)]
619
650
record_stream = vf.get_record_stream(keys, 'topological', False)
620
record = record_stream.next()
651
record = next(record_stream)
621
652
self.assertEqual(('rev-1',), record.key)
622
653
self.assertEqual([0, 0, 0], reload_counter)
623
record = record_stream.next()
654
record = next(record_stream)
624
655
self.assertEqual(('rev-2',), record.key)
625
656
self.assertEqual([1, 1, 0], reload_counter)
626
record = record_stream.next()
657
record = next(record_stream)
627
658
self.assertEqual(('rev-3',), record.key)
628
659
self.assertEqual([1, 1, 0], reload_counter)
629
660
# Now delete all pack files, and see that we raise the right error
630
for trans, name in vf._access._indices.itervalues():
661
for trans, name in vf._access._indices.values():
631
662
trans.delete(name)
632
663
self.assertListRaises(errors.NoSuchFile,
633
664
vf.get_record_stream, keys, 'topological', False)
651
682
self.assertEqual(plain_lines, reload_lines)
652
683
self.assertEqual(21, len(plain_lines))
653
684
# Now delete all pack files, and see that we raise the right error
654
for trans, name in vf._access._indices.itervalues():
685
for trans, name in vf._access._indices.values():
655
686
trans.delete(name)
656
687
self.assertListRaises(errors.NoSuchFile,
657
688
vf.iter_lines_added_or_present_in_keys, keys)
708
739
def make_multiple_records(self):
709
740
"""Create the content for multiple records."""
710
sha1sum = osutils.sha('foo\nbar\n').hexdigest()
741
sha1sum = osutils.sha_string('foo\nbar\n')
712
743
gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
717
748
record_1 = (0, len(gz_txt), sha1sum)
718
749
total_txt.append(gz_txt)
719
sha1sum = osutils.sha('baz\n').hexdigest()
750
sha1sum = osutils.sha_string('baz\n')
720
751
gz_txt = self.create_gz_content('version rev-id-2 1 %s\n'
726
757
return total_txt, record_1, record_2
728
759
def test_valid_knit_data(self):
729
sha1sum = osutils.sha('foo\nbar\n').hexdigest()
760
sha1sum = osutils.sha_string('foo\nbar\n')
730
761
gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
765
796
def test_not_enough_lines(self):
766
sha1sum = osutils.sha('foo\n').hexdigest()
797
sha1sum = osutils.sha_string('foo\n')
767
798
# record says 2 lines data says 1
768
799
gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
781
812
self.assertEqual([(('rev-id-1',), gz_txt, sha1sum)], raw_contents)
783
814
def test_too_many_lines(self):
784
sha1sum = osutils.sha('foo\nbar\n').hexdigest()
815
sha1sum = osutils.sha_string('foo\nbar\n')
785
816
# record says 1 lines data says 2
786
817
gz_txt = self.create_gz_content('version rev-id-1 1 %s\n'
800
831
self.assertEqual([(('rev-id-1',), gz_txt, sha1sum)], raw_contents)
802
833
def test_mismatched_version_id(self):
803
sha1sum = osutils.sha('foo\nbar\n').hexdigest()
834
sha1sum = osutils.sha_string('foo\nbar\n')
804
835
gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
819
850
knit._read_records_iter_raw(records))
821
852
def test_uncompressed_data(self):
822
sha1sum = osutils.sha('foo\nbar\n').hexdigest()
853
sha1sum = osutils.sha_string('foo\nbar\n')
823
854
txt = ('version rev-id-1 2 %s\n'
839
870
knit._read_records_iter_raw(records))
841
872
def test_corrupted_data(self):
842
sha1sum = osutils.sha('foo\nbar\n').hexdigest()
873
sha1sum = osutils.sha_string('foo\nbar\n')
843
874
gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
863
894
def get_knit_index(self, transport, name, mode):
864
895
mapper = ConstantMapper(name)
865
from bzrlib._knit_load_data_py import _load_data_py
896
from breezy._knit_load_data_py import _load_data_py
866
897
self.overrideAttr(knit, '_load_data', _load_data_py)
867
898
allow_writes = lambda: 'w' in mode
868
899
return _KndxIndex(transport, mapper, lambda:None, allow_writes, lambda:True)
872
903
index = self.get_knit_index(transport, "filename", "w")
874
905
call = transport.calls.pop(0)
875
# call[1][1] is a StringIO - we can't test it by simple equality.
906
# call[1][1] is a BytesIO - we can't test it by simple equality.
876
907
self.assertEqual('put_file_non_atomic', call[0])
877
908
self.assertEqual('filename.kndx', call[1][0])
878
909
# With no history, _KndxIndex writes a new index:
915
946
index = self.get_knit_index(transport, "filename", "r")
916
947
self.assertEqual(1, len(index.keys()))
917
self.assertEqual(set([("version",)]), index.keys())
948
self.assertEqual({("version",)}, index.keys())
919
950
def test_read_corrupted_header(self):
920
951
transport = MockTransport(['not a bzr knit index header\n'])
960
991
index.add_records([
961
992
((utf8_revision_id,), ["option"], ((utf8_revision_id,), 0, 1), [])])
962
993
call = transport.calls.pop(0)
963
# call[1][1] is a StringIO - we can't test it by simple equality.
994
# call[1][1] is a BytesIO - we can't test it by simple equality.
964
995
self.assertEqual('put_file_non_atomic', call[0])
965
996
self.assertEqual('filename.kndx', call[1][0])
966
997
# With no history, _KndxIndex writes a new index:
979
1010
index.add_records([
980
1011
(("version",), ["option"], (("version",), 0, 1), [(utf8_revision_id,)])])
981
1012
call = transport.calls.pop(0)
982
# call[1][1] is a StringIO - we can't test it by simple equality.
1013
# call[1][1] is a BytesIO - we can't test it by simple equality.
983
1014
self.assertEqual('put_file_non_atomic', call[0])
984
1015
self.assertEqual('filename.kndx', call[1][0])
985
1016
# With no history, _KndxIndex writes a new index:
997
1028
self.assertEqual(set(), index.keys())
999
1030
index.add_records([(("a",), ["option"], (("a",), 0, 1), [])])
1000
self.assertEqual(set([("a",)]), index.keys())
1031
self.assertEqual({("a",)}, index.keys())
1002
1033
index.add_records([(("a",), ["option"], (("a",), 0, 1), [])])
1003
self.assertEqual(set([("a",)]), index.keys())
1034
self.assertEqual({("a",)}, index.keys())
1005
1036
index.add_records([(("b",), ["option"], (("b",), 0, 1), [])])
1006
self.assertEqual(set([("a",), ("b",)]), index.keys())
1037
self.assertEqual({("a",), ("b",)}, index.keys())
1008
1039
def add_a_b(self, index, random_id=None):
1034
1065
self.add_a_b(index)
1035
1066
call = transport.calls.pop(0)
1036
# call[1][1] is a StringIO - we can't test it by simple equality.
1067
# call[1][1] is a BytesIO - we can't test it by simple equality.
1037
1068
self.assertEqual('put_file_non_atomic', call[0])
1038
1069
self.assertEqual('filename.kndx', call[1][0])
1039
1070
# With no history, _KndxIndex writes a new index:
1073
1104
self.assertEqual(_KndxIndex.HEADER, call[1][1].getvalue())
1074
1105
self.assertEqual({'create_parent_dir': True}, call[2])
1075
1106
call = transport.calls.pop(0)
1076
# call[1][1] is a StringIO - we can't test it by simple equality.
1107
# call[1][1] is a BytesIO - we can't test it by simple equality.
1077
1108
self.assertEqual('put_file_non_atomic', call[0])
1078
1109
self.assertEqual('filename.kndx', call[1][0])
1079
1110
# With no history, _KndxIndex writes a new index:
1165
1196
index = self.get_knit_index(transport, 'filename', 'r')
1167
1198
self.assertRaises(errors.KnitCorrupt, index.keys)
1168
except TypeError, e:
1199
except TypeError as e:
1169
1200
if (str(e) == ('exceptions must be strings, classes, or instances,'
1170
' not exceptions.IndexError')
1171
and sys.version_info[0:2] >= (2,5)):
1201
' not exceptions.IndexError')):
1172
1202
self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
1173
1203
' raising new style exceptions with python'
1185
1215
index = self.get_knit_index(transport, 'filename', 'r')
1187
1217
self.assertRaises(errors.KnitCorrupt, index.keys)
1188
except TypeError, e:
1218
except TypeError as e:
1189
1219
if (str(e) == ('exceptions must be strings, classes, or instances,'
1190
' not exceptions.ValueError')
1191
and sys.version_info[0:2] >= (2,5)):
1220
' not exceptions.ValueError')):
1192
1221
self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
1193
1222
' raising new style exceptions with python'
1205
1234
index = self.get_knit_index(transport, 'filename', 'r')
1207
1236
self.assertRaises(errors.KnitCorrupt, index.keys)
1208
except TypeError, e:
1237
except TypeError as e:
1209
1238
if (str(e) == ('exceptions must be strings, classes, or instances,'
1210
' not exceptions.ValueError')
1211
and sys.version_info[0:2] >= (2,5)):
1239
' not exceptions.ValueError')):
1212
1240
self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
1213
1241
' raising new style exceptions with python'
1223
1251
index = self.get_knit_index(transport, 'filename', 'r')
1225
1253
self.assertRaises(errors.KnitCorrupt, index.keys)
1226
except TypeError, e:
1254
except TypeError as e:
1227
1255
if (str(e) == ('exceptions must be strings, classes, or instances,'
1228
' not exceptions.ValueError')
1229
and sys.version_info[0:2] >= (2,5)):
1256
' not exceptions.ValueError')):
1230
1257
self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
1231
1258
' raising new style exceptions with python'
1241
1268
index = self.get_knit_index(transport, 'filename', 'r')
1243
1270
self.assertRaises(errors.KnitCorrupt, index.keys)
1244
except TypeError, e:
1271
except TypeError as e:
1245
1272
if (str(e) == ('exceptions must be strings, classes, or instances,'
1246
' not exceptions.ValueError')
1247
and sys.version_info[0:2] >= (2,5)):
1273
' not exceptions.ValueError')):
1248
1274
self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
1249
1275
' raising new style exceptions with python'
1267
1293
"b option 10 10 0", # This line isn't terminated, ignored
1269
1295
index = self.get_knit_index(transport, "filename", "r")
1270
self.assertEqual(set([('a',)]), index.keys())
1296
self.assertEqual({('a',)}, index.keys())
1272
1298
def test_skip_incomplete_record(self):
1273
1299
# A line with bogus data should just be skipped
1278
1304
"c option 20 10 0 :", # Properly terminated, and starts with '\n'
1280
1306
index = self.get_knit_index(transport, "filename", "r")
1281
self.assertEqual(set([('a',), ('c',)]), index.keys())
1307
self.assertEqual({('a',), ('c',)}, index.keys())
1283
1309
def test_trailing_characters(self):
1284
1310
# A line with bogus data should just be skipped
1289
1315
"c option 20 10 0 :", # Properly terminated, and starts with '\n'
1291
1317
index = self.get_knit_index(transport, "filename", "r")
1292
self.assertEqual(set([('a',), ('c',)]), index.keys())
1318
self.assertEqual({('a',), ('c',)}, index.keys())
1295
1321
class LowLevelKnitIndexTests_c(LowLevelKnitIndexTests):
1299
1325
def get_knit_index(self, transport, name, mode):
1300
1326
mapper = ConstantMapper(name)
1301
from bzrlib._knit_load_data_pyx import _load_data_c
1327
from breezy._knit_load_data_pyx import _load_data_c
1302
1328
self.overrideAttr(knit, '_load_data', _load_data_c)
1303
1329
allow_writes = lambda: mode == 'w'
1304
1330
return _KndxIndex(transport, mapper, lambda:None,
1525
1551
'a-2 fulltext 0 0 0 :\n'
1526
1552
'a-3 fulltext 0 0 1 :'
1528
self.assertEqual(set([('a-3',), ('a-1',), ('a-2',)]), idx.keys())
1554
self.assertEqual({('a-3',), ('a-1',), ('a-2',)}, idx.keys())
1529
1555
self.assertEqual({
1530
1556
('a-1',): ((('a-1',), 0, 0), None, (), ('fulltext', False)),
1531
1557
('a-2',): ((('a-2',), 0, 0), None, (('a-1',),), ('fulltext', False)),
1564
1590
# Assert the pre-condition
1565
1591
def assertA1Only():
1566
self.assertEqual(set([('a-1',)]), set(idx.keys()))
1592
self.assertEqual({('a-1',)}, set(idx.keys()))
1567
1593
self.assertEqual(
1568
1594
{('a-1',): ((('a-1',), 0, 0), None, (), ('fulltext', False))},
1569
1595
idx.get_build_details([('a-1',)]))
1579
1605
# could leave an empty .kndx file, which bzr would later claim was a
1580
1606
# corrupted file since the header was not present. In reality, the file
1581
1607
# just wasn't created, so it should be ignored.
1582
t = get_transport('.')
1608
t = transport.get_transport_from_path('.')
1583
1609
t.put_bytes('test.kndx', '')
1585
1611
knit = self.make_test_knit()
1587
1613
def test_knit_index_checks_header(self):
1588
t = get_transport('.')
1614
t = transport.get_transport_from_path('.')
1589
1615
t.put_bytes('test.kndx', '# not really a knit header\n\n')
1590
1616
k = self.make_test_knit()
1591
1617
self.assertRaises(KnitHeaderError, k.keys)
1639
1665
def test_keys(self):
1640
1666
index = self.two_graph_index()
1641
self.assertEqual(set([('tail',), ('tip',), ('parent',), ('separate',)]),
1667
self.assertEqual({('tail',), ('tip',), ('parent',), ('separate',)},
1642
1668
set(index.keys()))
1644
1670
def test_get_position(self):
1941
1967
def test_keys(self):
1942
1968
index = self.two_graph_index()
1943
self.assertEqual(set([('tail',), ('tip',), ('parent',), ('separate',)]),
1969
self.assertEqual({('tail',), ('tip',), ('parent',), ('separate',)},
1944
1970
set(index.keys()))
1946
1972
def test_get_position(self):
2105
2131
self.assertGroupKeysForIo([([f_a], set())],
2106
2132
[f_a], [], positions)
2107
self.assertGroupKeysForIo([([f_a], set([f_a]))],
2133
self.assertGroupKeysForIo([([f_a], {f_a})],
2108
2134
[f_a], [f_a], positions)
2109
2135
self.assertGroupKeysForIo([([f_a, f_b], set([]))],
2110
2136
[f_a, f_b], [], positions)
2111
self.assertGroupKeysForIo([([f_a, f_b], set([f_b]))],
2137
self.assertGroupKeysForIo([([f_a, f_b], {f_b})],
2112
2138
[f_a, f_b], [f_b], positions)
2113
2139
self.assertGroupKeysForIo([([f_a, f_b, g_a, g_b], set())],
2114
2140
[f_a, g_a, f_b, g_b], [], positions)
2211
2237
self.assertEqual([(key_basis, 'foo\n'), (key_basis, 'bar\n')], details)
2212
2238
# Not optimised to date:
2213
2239
# self.assertEqual([("annotate", key_basis)], basis.calls)
2214
self.assertEqual([('get_parent_map', set([key_basis])),
2215
('get_parent_map', set([key_basis])),
2240
self.assertEqual([('get_parent_map', {key_basis}),
2241
('get_parent_map', {key_basis}),
2216
2242
('get_record_stream', [key_basis], 'topological', True)],
2238
2264
parent_map = test.get_parent_map([key, key_basis, key_missing])
2239
2265
self.assertEqual({key: (),
2240
2266
key_basis: ()}, parent_map)
2241
self.assertEqual([("get_parent_map", set([key_basis, key_missing]))],
2267
self.assertEqual([("get_parent_map", {key_basis, key_missing})],
2244
2270
def test_get_record_stream_unordered_fulltexts(self):
2275
2301
# It's not strictly minimal, but it seems reasonable for now for it to
2276
2302
# ask which fallbacks have which parents.
2277
2303
self.assertEqual([
2278
("get_parent_map", set([key_basis, key_missing])),
2304
("get_parent_map", {key_basis, key_missing}),
2279
2305
("get_record_stream", [key_basis], 'unordered', True)],
2315
record = source.get_record_stream([result[0]], 'unordered',
2341
record = next(source.get_record_stream([result[0]], 'unordered',
2317
2343
self.assertEqual(record.key, result[0])
2318
2344
self.assertEqual(record.sha1, result[1])
2319
2345
# We used to check that the storage kind matched, but actually it
2324
2350
# It's not strictly minimal, but it seems reasonable for now for it to
2325
2351
# ask which fallbacks have which parents.
2326
2352
self.assertEqual([
2327
("get_parent_map", set([key_basis, key_basis_2, key_missing])),
2353
("get_parent_map", {key_basis, key_basis_2, key_missing}),
2328
2354
# topological is requested from the fallback, because that is what
2329
2355
# was requested at the top level.
2330
2356
("get_record_stream", [key_basis_2, key_basis], 'topological', True)],
2362
2388
# It's not strictly minimal, but it seems reasonable for now for it to
2363
2389
# ask which fallbacks have which parents.
2364
2390
self.assertEqual([
2365
("get_parent_map", set([key_basis, key_missing])),
2391
("get_parent_map", {key_basis, key_missing}),
2366
2392
("get_record_stream", [key_basis], 'unordered', False)],
2402
record = source.get_record_stream([result[0]], 'unordered',
2428
record = next(source.get_record_stream([result[0]], 'unordered',
2404
2430
self.assertEqual(record.key, result[0])
2405
2431
self.assertEqual(record.sha1, result[1])
2406
2432
self.assertEqual(record.storage_kind, result[2])
2408
2434
# It's not strictly minimal, but it seems reasonable for now for it to
2409
2435
# ask which fallbacks have which parents.
2410
2436
self.assertEqual([
2411
("get_parent_map", set([key_basis, key_basis_2, key_missing])),
2437
("get_parent_map", {key_basis, key_basis_2, key_missing}),
2412
2438
("get_record_stream", [key_basis_2, key_basis], 'topological', False)],
2419
2445
key_basis = ('bar',)
2420
2446
key_missing = ('missing',)
2421
2447
test.add_lines(key, (), ['foo\n'])
2422
key_sha1sum = osutils.sha('foo\n').hexdigest()
2448
key_sha1sum = osutils.sha_string('foo\n')
2423
2449
sha1s = test.get_sha1s([key])
2424
2450
self.assertEqual({key: key_sha1sum}, sha1s)
2425
2451
self.assertEqual([], basis.calls)
2427
2453
# directly (rather than via text reconstruction) so that remote servers
2428
2454
# etc don't have to answer with full content.
2429
2455
basis.add_lines(key_basis, (), ['foo\n', 'bar\n'])
2430
basis_sha1sum = osutils.sha('foo\nbar\n').hexdigest()
2456
basis_sha1sum = osutils.sha_string('foo\nbar\n')
2431
2457
basis.calls = []
2432
2458
sha1s = test.get_sha1s([key, key_missing, key_basis])
2433
2459
self.assertEqual({key: key_sha1sum,
2434
2460
key_basis: basis_sha1sum}, sha1s)
2435
self.assertEqual([("get_sha1s", set([key_basis, key_missing]))],
2461
self.assertEqual([("get_sha1s", {key_basis, key_missing})],
2438
2464
def test_insert_record_stream(self):
2451
2477
test.insert_record_stream(stream)
2452
2478
# XXX: this does somewhat too many calls in making sure of whether it
2453
2479
# has to recreate the full text.
2454
self.assertEqual([("get_parent_map", set([key_basis])),
2455
('get_parent_map', set([key_basis])),
2480
self.assertEqual([("get_parent_map", {key_basis}),
2481
('get_parent_map', {key_basis}),
2456
2482
('get_record_stream', [key_basis], 'unordered', True)],
2458
2484
self.assertEqual({key_delta:(key_basis,)},
2471
2497
basis.calls = []
2472
2498
lines = list(test.iter_lines_added_or_present_in_keys([key1]))
2473
2499
self.assertEqual([("foo\n", key1)], lines)
2474
self.assertEqual([("iter_lines_added_or_present_in_keys", set([key1]))],
2500
self.assertEqual([("iter_lines_added_or_present_in_keys", {key1})],
2476
2502
# keys in both are not duplicated:
2477
2503
test.add_lines(key2, (), ["bar\n"])
2493
2519
basis.add_lines(key1, (), [])
2494
2520
basis.calls = []
2495
2521
keys = test.keys()
2496
self.assertEqual(set([key1]), set(keys))
2522
self.assertEqual({key1}, set(keys))
2497
2523
self.assertEqual([("keys",)], basis.calls)
2498
2524
# keys in both are not duplicated:
2499
2525
test.add_lines(key2, (), [])
2501
2527
basis.calls = []
2502
2528
keys = test.keys()
2503
2529
self.assertEqual(2, len(keys))
2504
self.assertEqual(set([key1, key2]), set(keys))
2530
self.assertEqual({key1, key2}, set(keys))
2505
2531
self.assertEqual([("keys",)], basis.calls)
2507
2533
def test_add_mpdiffs(self):
2519
2545
diffs = source.make_mpdiffs([key_delta])
2520
2546
test.add_mpdiffs([(key_delta, (key_basis,),
2521
2547
source.get_sha1s([key_delta])[key_delta], diffs[0])])
2522
self.assertEqual([("get_parent_map", set([key_basis])),
2548
self.assertEqual([("get_parent_map", {key_basis}),
2523
2549
('get_record_stream', [key_basis], 'unordered', True),],
2525
2551
self.assertEqual({key_delta:(key_basis,)},
2548
2574
self.assertEqual(3, len(basis.calls))
2549
2575
self.assertEqual([
2550
("get_parent_map", set([key_left, key_right])),
2551
("get_parent_map", set([key_left, key_right])),
2576
("get_parent_map", {key_left, key_right}),
2577
("get_parent_map", {key_left, key_right}),
2553
2579
basis.calls[:-1])
2554
2580
last_call = basis.calls[-1]
2555
2581
self.assertEqual('get_record_stream', last_call[0])
2556
self.assertEqual(set([key_left, key_right]), set(last_call[1]))
2582
self.assertEqual({key_left, key_right}, set(last_call[1]))
2557
2583
self.assertEqual('topological', last_call[2])
2558
2584
self.assertEqual(True, last_call[3])