260
110
return StringIO("\n".join(self.file_lines))
262
def readv(self, relpath, offsets):
263
fp = self.get(relpath)
264
for offset, size in offsets:
266
yield offset, fp.read(size)
268
112
def __getattr__(self, name):
269
113
def queue_call(*args, **kwargs):
270
114
self.calls.append((name, args, kwargs))
271
115
return queue_call
274
class KnitRecordAccessTestsMixin(object):
275
"""Tests for getting and putting knit records."""
277
def test_add_raw_records(self):
278
"""Add_raw_records adds records retrievable later."""
279
access = self.get_access()
280
memos = access.add_raw_records([('key', 10)], '1234567890')
281
self.assertEqual(['1234567890'], list(access.get_raw_records(memos)))
283
def test_add_several_raw_records(self):
284
"""add_raw_records with many records and read some back."""
285
access = self.get_access()
286
memos = access.add_raw_records([('key', 10), ('key2', 2), ('key3', 5)],
288
self.assertEqual(['1234567890', '12', '34567'],
289
list(access.get_raw_records(memos)))
290
self.assertEqual(['1234567890'],
291
list(access.get_raw_records(memos[0:1])))
292
self.assertEqual(['12'],
293
list(access.get_raw_records(memos[1:2])))
294
self.assertEqual(['34567'],
295
list(access.get_raw_records(memos[2:3])))
296
self.assertEqual(['1234567890', '34567'],
297
list(access.get_raw_records(memos[0:1] + memos[2:3])))
300
class TestKnitKnitAccess(TestCaseWithMemoryTransport, KnitRecordAccessTestsMixin):
301
"""Tests for the .kndx implementation."""
303
def get_access(self):
304
"""Get a .knit style access instance."""
305
mapper = ConstantMapper("foo")
306
access = _KnitKeyAccess(self.get_transport(), mapper)
310
class TestPackKnitAccess(TestCaseWithMemoryTransport, KnitRecordAccessTestsMixin):
311
"""Tests for the pack based access."""
313
def get_access(self):
314
return self._get_access()[0]
316
def _get_access(self, packname='packfile', index='FOO'):
317
transport = self.get_transport()
318
def write_data(bytes):
319
transport.append_bytes(packname, bytes)
320
writer = pack.ContainerWriter(write_data)
322
access = _DirectPackAccess({})
323
access.set_writer(writer, index, (transport, packname))
324
return access, writer
326
def test_read_from_several_packs(self):
327
access, writer = self._get_access()
329
memos.extend(access.add_raw_records([('key', 10)], '1234567890'))
331
access, writer = self._get_access('pack2', 'FOOBAR')
332
memos.extend(access.add_raw_records([('key', 5)], '12345'))
334
access, writer = self._get_access('pack3', 'BAZ')
335
memos.extend(access.add_raw_records([('key', 5)], 'alpha'))
337
transport = self.get_transport()
338
access = _DirectPackAccess({"FOO":(transport, 'packfile'),
339
"FOOBAR":(transport, 'pack2'),
340
"BAZ":(transport, 'pack3')})
341
self.assertEqual(['1234567890', '12345', 'alpha'],
342
list(access.get_raw_records(memos)))
343
self.assertEqual(['1234567890'],
344
list(access.get_raw_records(memos[0:1])))
345
self.assertEqual(['12345'],
346
list(access.get_raw_records(memos[1:2])))
347
self.assertEqual(['alpha'],
348
list(access.get_raw_records(memos[2:3])))
349
self.assertEqual(['1234567890', 'alpha'],
350
list(access.get_raw_records(memos[0:1] + memos[2:3])))
352
def test_set_writer(self):
353
"""The writer should be settable post construction."""
354
access = _DirectPackAccess({})
355
transport = self.get_transport()
356
packname = 'packfile'
358
def write_data(bytes):
359
transport.append_bytes(packname, bytes)
360
writer = pack.ContainerWriter(write_data)
362
access.set_writer(writer, index, (transport, packname))
363
memos = access.add_raw_records([('key', 10)], '1234567890')
365
self.assertEqual(['1234567890'], list(access.get_raw_records(memos)))
368
class LowLevelKnitDataTests(TestCase):
370
def create_gz_content(self, text):
372
gz_file = gzip.GzipFile(mode='wb', fileobj=sio)
375
return sio.getvalue()
377
def test_valid_knit_data(self):
378
sha1sum = sha.new('foo\nbar\n').hexdigest()
379
gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
384
transport = MockTransport([gz_txt])
385
access = _KnitKeyAccess(transport, ConstantMapper('filename'))
386
knit = KnitVersionedFiles(None, access)
387
records = [(('rev-id-1',), (('rev-id-1',), 0, len(gz_txt)))]
389
contents = list(knit._read_records_iter(records))
390
self.assertEqual([(('rev-id-1',), ['foo\n', 'bar\n'],
391
'4e48e2c9a3d2ca8a708cb0cc545700544efb5021')], contents)
393
raw_contents = list(knit._read_records_iter_raw(records))
394
self.assertEqual([(('rev-id-1',), gz_txt, sha1sum)], raw_contents)
396
def test_not_enough_lines(self):
397
sha1sum = sha.new('foo\n').hexdigest()
398
# record says 2 lines data says 1
399
gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
403
transport = MockTransport([gz_txt])
404
access = _KnitKeyAccess(transport, ConstantMapper('filename'))
405
knit = KnitVersionedFiles(None, access)
406
records = [(('rev-id-1',), (('rev-id-1',), 0, len(gz_txt)))]
407
self.assertRaises(errors.KnitCorrupt, list,
408
knit._read_records_iter(records))
410
# read_records_iter_raw won't detect that sort of mismatch/corruption
411
raw_contents = list(knit._read_records_iter_raw(records))
412
self.assertEqual([(('rev-id-1',), gz_txt, sha1sum)], raw_contents)
414
def test_too_many_lines(self):
415
sha1sum = sha.new('foo\nbar\n').hexdigest()
416
# record says 1 lines data says 2
417
gz_txt = self.create_gz_content('version rev-id-1 1 %s\n'
422
transport = MockTransport([gz_txt])
423
access = _KnitKeyAccess(transport, ConstantMapper('filename'))
424
knit = KnitVersionedFiles(None, access)
425
records = [(('rev-id-1',), (('rev-id-1',), 0, len(gz_txt)))]
426
self.assertRaises(errors.KnitCorrupt, list,
427
knit._read_records_iter(records))
429
# read_records_iter_raw won't detect that sort of mismatch/corruption
430
raw_contents = list(knit._read_records_iter_raw(records))
431
self.assertEqual([(('rev-id-1',), gz_txt, sha1sum)], raw_contents)
433
def test_mismatched_version_id(self):
434
sha1sum = sha.new('foo\nbar\n').hexdigest()
435
gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
440
transport = MockTransport([gz_txt])
441
access = _KnitKeyAccess(transport, ConstantMapper('filename'))
442
knit = KnitVersionedFiles(None, access)
443
# We are asking for rev-id-2, but the data is rev-id-1
444
records = [(('rev-id-2',), (('rev-id-2',), 0, len(gz_txt)))]
445
self.assertRaises(errors.KnitCorrupt, list,
446
knit._read_records_iter(records))
448
# read_records_iter_raw detects mismatches in the header
449
self.assertRaises(errors.KnitCorrupt, list,
450
knit._read_records_iter_raw(records))
452
def test_uncompressed_data(self):
453
sha1sum = sha.new('foo\nbar\n').hexdigest()
454
txt = ('version rev-id-1 2 %s\n'
459
transport = MockTransport([txt])
460
access = _KnitKeyAccess(transport, ConstantMapper('filename'))
461
knit = KnitVersionedFiles(None, access)
462
records = [(('rev-id-1',), (('rev-id-1',), 0, len(txt)))]
464
# We don't have valid gzip data ==> corrupt
465
self.assertRaises(errors.KnitCorrupt, list,
466
knit._read_records_iter(records))
468
# read_records_iter_raw will notice the bad data
469
self.assertRaises(errors.KnitCorrupt, list,
470
knit._read_records_iter_raw(records))
472
def test_corrupted_data(self):
473
sha1sum = sha.new('foo\nbar\n').hexdigest()
474
gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
479
# Change 2 bytes in the middle to \xff
480
gz_txt = gz_txt[:10] + '\xff\xff' + gz_txt[12:]
481
transport = MockTransport([gz_txt])
482
access = _KnitKeyAccess(transport, ConstantMapper('filename'))
483
knit = KnitVersionedFiles(None, access)
484
records = [(('rev-id-1',), (('rev-id-1',), 0, len(gz_txt)))]
485
self.assertRaises(errors.KnitCorrupt, list,
486
knit._read_records_iter(records))
487
# read_records_iter_raw will barf on bad gz data
488
self.assertRaises(errors.KnitCorrupt, list,
489
knit._read_records_iter_raw(records))
492
118
class LowLevelKnitIndexTests(TestCase):
494
def get_knit_index(self, transport, name, mode):
495
mapper = ConstantMapper(name)
496
orig = knit._load_data
498
knit._load_data = orig
499
self.addCleanup(reset)
500
from bzrlib._knit_load_data_py import _load_data_py
501
knit._load_data = _load_data_py
502
allow_writes = lambda: 'w' in mode
503
return _KndxIndex(transport, mapper, lambda:None, allow_writes, lambda:True)
120
def test_no_such_file(self):
121
transport = MockTransport()
123
self.assertRaises(NoSuchFile, _KnitIndex, transport, "filename", "r")
124
self.assertRaises(NoSuchFile, _KnitIndex, transport,
125
"filename", "w", create=False)
505
127
def test_create_file(self):
506
128
transport = MockTransport()
507
index = self.get_knit_index(transport, "filename", "w")
509
call = transport.calls.pop(0)
510
# call[1][1] is a StringIO - we can't test it by simple equality.
511
self.assertEqual('put_file_non_atomic', call[0])
512
self.assertEqual('filename.kndx', call[1][0])
513
# With no history, _KndxIndex writes a new index:
514
self.assertEqual(_KndxIndex.HEADER,
515
call[1][1].getvalue())
516
self.assertEqual({'create_parent_dir': True}, call[2])
130
index = _KnitIndex(transport, "filename", "w",
131
file_mode="wb", create=True)
133
("put_bytes_non_atomic",
134
("filename", index.HEADER), {"mode": "wb"}),
135
transport.calls.pop(0))
137
def test_delay_create_file(self):
138
transport = MockTransport()
140
index = _KnitIndex(transport, "filename", "w",
141
create=True, file_mode="wb", create_parent_dir=True,
142
delay_create=True, dir_mode=0777)
143
self.assertEqual([], transport.calls)
145
index.add_versions([])
146
name, (filename, f), kwargs = transport.calls.pop(0)
147
self.assertEqual("put_file_non_atomic", name)
149
{"dir_mode": 0777, "create_parent_dir": True, "mode": "wb"},
151
self.assertEqual("filename", filename)
152
self.assertEqual(index.HEADER, f.read())
154
index.add_versions([])
155
self.assertEqual(("append_bytes", ("filename", ""), {}),
156
transport.calls.pop(0))
518
158
def test_read_utf8_version_id(self):
519
unicode_revision_id = u"version-\N{CYRILLIC CAPITAL LETTER A}"
520
utf8_revision_id = unicode_revision_id.encode('utf-8')
521
159
transport = MockTransport([
523
'%s option 0 1 :' % (utf8_revision_id,)
161
u"version-\N{CYRILLIC CAPITAL LETTER A}"
162
u" option 0 1 :".encode("utf-8")
525
index = self.get_knit_index(transport, "filename", "r")
526
# _KndxIndex is a private class, and deals in utf8 revision_ids, not
527
# Unicode revision_ids.
528
self.assertEqual({(utf8_revision_id,):()},
529
index.get_parent_map(index.keys()))
530
self.assertFalse((unicode_revision_id,) in index.keys())
164
index = _KnitIndex(transport, "filename", "r")
166
index.has_version(u"version-\N{CYRILLIC CAPITAL LETTER A}"))
532
168
def test_read_utf8_parents(self):
533
unicode_revision_id = u"version-\N{CYRILLIC CAPITAL LETTER A}"
534
utf8_revision_id = unicode_revision_id.encode('utf-8')
535
169
transport = MockTransport([
537
"version option 0 1 .%s :" % (utf8_revision_id,)
171
u"version option 0 1"
172
u" .version-\N{CYRILLIC CAPITAL LETTER A} :".encode("utf-8")
539
index = self.get_knit_index(transport, "filename", "r")
540
self.assertEqual({("version",):((utf8_revision_id,),)},
541
index.get_parent_map(index.keys()))
174
index = _KnitIndex(transport, "filename", "r")
175
self.assertEqual([u"version-\N{CYRILLIC CAPITAL LETTER A}"],
176
index.get_parents_with_ghosts("version"))
543
178
def test_read_ignore_corrupted_lines(self):
544
179
transport = MockTransport([
547
182
"corrupted options 0 1 .b .c ",
548
183
"version options 0 1 :"
550
index = self.get_knit_index(transport, "filename", "r")
551
self.assertEqual(1, len(index.keys()))
552
self.assertEqual(set([("version",)]), index.keys())
185
index = _KnitIndex(transport, "filename", "r")
186
self.assertEqual(1, index.num_versions())
187
self.assertTrue(index.has_version(u"version"))
554
189
def test_read_corrupted_header(self):
555
190
transport = MockTransport(['not a bzr knit index header\n'])
556
index = self.get_knit_index(transport, "filename", "r")
557
self.assertRaises(KnitHeaderError, index.keys)
191
self.assertRaises(KnitHeaderError,
192
_KnitIndex, transport, "filename", "r")
559
194
def test_read_duplicate_entries(self):
560
195
transport = MockTransport([
562
197
"parent options 0 1 :",
563
198
"version options1 0 1 0 :",
564
199
"version options2 1 2 .other :",
565
200
"version options3 3 4 0 .other :"
567
index = self.get_knit_index(transport, "filename", "r")
568
self.assertEqual(2, len(index.keys()))
569
# check that the index used is the first one written. (Specific
570
# to KnitIndex style indices.
571
self.assertEqual("1", index._dictionary_compress([("version",)]))
572
self.assertEqual((("version",), 3, 4), index.get_position(("version",)))
573
self.assertEqual(["options3"], index.get_options(("version",)))
574
self.assertEqual({("version",):(("parent",), ("other",))},
575
index.get_parent_map([("version",)]))
202
index = _KnitIndex(transport, "filename", "r")
203
self.assertEqual(2, index.num_versions())
204
self.assertEqual(1, index.lookup(u"version"))
205
self.assertEqual((3, 4), index.get_position(u"version"))
206
self.assertEqual(["options3"], index.get_options(u"version"))
207
self.assertEqual([u"parent", u"other"],
208
index.get_parents_with_ghosts(u"version"))
577
210
def test_read_compressed_parents(self):
578
211
transport = MockTransport([
580
213
"a option 0 1 :",
581
214
"b option 0 1 0 :",
582
215
"c option 0 1 1 0 :",
584
index = self.get_knit_index(transport, "filename", "r")
585
self.assertEqual({("b",):(("a",),), ("c",):(("b",), ("a",))},
586
index.get_parent_map([("b",), ("c",)]))
217
index = _KnitIndex(transport, "filename", "r")
218
self.assertEqual([u"a"], index.get_parents(u"b"))
219
self.assertEqual([u"b", u"a"], index.get_parents(u"c"))
588
221
def test_write_utf8_version_id(self):
589
unicode_revision_id = u"version-\N{CYRILLIC CAPITAL LETTER A}"
590
utf8_revision_id = unicode_revision_id.encode('utf-8')
591
222
transport = MockTransport([
594
index = self.get_knit_index(transport, "filename", "r")
596
((utf8_revision_id,), ["option"], ((utf8_revision_id,), 0, 1), [])])
597
call = transport.calls.pop(0)
598
# call[1][1] is a StringIO - we can't test it by simple equality.
599
self.assertEqual('put_file_non_atomic', call[0])
600
self.assertEqual('filename.kndx', call[1][0])
601
# With no history, _KndxIndex writes a new index:
602
self.assertEqual(_KndxIndex.HEADER +
603
"\n%s option 0 1 :" % (utf8_revision_id,),
604
call[1][1].getvalue())
605
self.assertEqual({'create_parent_dir': True}, call[2])
225
index = _KnitIndex(transport, "filename", "r")
226
index.add_version(u"version-\N{CYRILLIC CAPITAL LETTER A}",
227
["option"], 0, 1, [])
228
self.assertEqual(("append_bytes", ("filename",
229
u"\nversion-\N{CYRILLIC CAPITAL LETTER A}"
230
u" option 0 1 :".encode("utf-8")),
232
transport.calls.pop(0))
607
234
def test_write_utf8_parents(self):
608
unicode_revision_id = u"version-\N{CYRILLIC CAPITAL LETTER A}"
609
utf8_revision_id = unicode_revision_id.encode('utf-8')
610
transport = MockTransport([
613
index = self.get_knit_index(transport, "filename", "r")
615
(("version",), ["option"], (("version",), 0, 1), [(utf8_revision_id,)])])
616
call = transport.calls.pop(0)
617
# call[1][1] is a StringIO - we can't test it by simple equality.
618
self.assertEqual('put_file_non_atomic', call[0])
619
self.assertEqual('filename.kndx', call[1][0])
620
# With no history, _KndxIndex writes a new index:
621
self.assertEqual(_KndxIndex.HEADER +
622
"\nversion option 0 1 .%s :" % (utf8_revision_id,),
623
call[1][1].getvalue())
624
self.assertEqual({'create_parent_dir': True}, call[2])
627
transport = MockTransport([
630
index = self.get_knit_index(transport, "filename", "r")
632
self.assertEqual(set(), index.keys())
634
index.add_records([(("a",), ["option"], (("a",), 0, 1), [])])
635
self.assertEqual(set([("a",)]), index.keys())
637
index.add_records([(("a",), ["option"], (("a",), 0, 1), [])])
638
self.assertEqual(set([("a",)]), index.keys())
640
index.add_records([(("b",), ["option"], (("b",), 0, 1), [])])
641
self.assertEqual(set([("a",), ("b",)]), index.keys())
643
def add_a_b(self, index, random_id=None):
645
if random_id is not None:
646
kwargs["random_id"] = random_id
648
(("a",), ["option"], (("a",), 0, 1), [("b",)]),
649
(("a",), ["opt"], (("a",), 1, 2), [("c",)]),
650
(("b",), ["option"], (("b",), 2, 3), [("a",)])
653
def assertIndexIsAB(self, index):
658
index.get_parent_map(index.keys()))
659
self.assertEqual((("a",), 1, 2), index.get_position(("a",)))
660
self.assertEqual((("b",), 2, 3), index.get_position(("b",)))
661
self.assertEqual(["opt"], index.get_options(("a",)))
235
transport = MockTransport([
238
index = _KnitIndex(transport, "filename", "r")
239
index.add_version(u"version", ["option"], 0, 1,
240
[u"version-\N{CYRILLIC CAPITAL LETTER A}"])
241
self.assertEqual(("append_bytes", ("filename",
242
u"\nversion option 0 1"
243
u" .version-\N{CYRILLIC CAPITAL LETTER A} :".encode("utf-8")),
245
transport.calls.pop(0))
247
def test_get_graph(self):
248
transport = MockTransport()
249
index = _KnitIndex(transport, "filename", "w", create=True)
250
self.assertEqual([], index.get_graph())
252
index.add_version(u"a", ["option"], 0, 1, [u"b"])
253
self.assertEqual([(u"a", [u"b"])], index.get_graph())
255
index.add_version(u"c", ["option"], 0, 1, [u"d"])
256
self.assertEqual([(u"a", [u"b"]), (u"c", [u"d"])],
257
sorted(index.get_graph()))
259
def test_get_ancestry(self):
260
transport = MockTransport([
263
"b option 0 1 0 .e :",
264
"c option 0 1 1 0 :",
265
"d option 0 1 2 .f :"
267
index = _KnitIndex(transport, "filename", "r")
269
self.assertEqual([], index.get_ancestry([]))
270
self.assertEqual([u"a"], index.get_ancestry([u"a"]))
271
self.assertEqual([u"a", u"b"], index.get_ancestry([u"b"]))
272
self.assertEqual([u"a", u"b", u"c"], index.get_ancestry([u"c"]))
273
self.assertEqual([u"a", u"b", u"c", u"d"], index.get_ancestry([u"d"]))
274
self.assertEqual([u"a", u"b"], index.get_ancestry([u"a", u"b"]))
275
self.assertEqual([u"a", u"b", u"c"], index.get_ancestry([u"a", u"c"]))
277
self.assertRaises(RevisionNotPresent, index.get_ancestry, [u"e"])
279
def test_get_ancestry_with_ghosts(self):
280
transport = MockTransport([
283
"b option 0 1 0 .e :",
284
"c option 0 1 0 .f .g :",
285
"d option 0 1 2 .h .j .k :"
287
index = _KnitIndex(transport, "filename", "r")
289
self.assertEqual([], index.get_ancestry_with_ghosts([]))
290
self.assertEqual([u"a"], index.get_ancestry_with_ghosts([u"a"]))
291
self.assertEqual([u"a", u"e", u"b"],
292
index.get_ancestry_with_ghosts([u"b"]))
293
self.assertEqual([u"a", u"g", u"f", u"c"],
294
index.get_ancestry_with_ghosts([u"c"]))
295
self.assertEqual([u"a", u"g", u"f", u"c", u"k", u"j", u"h", u"d"],
296
index.get_ancestry_with_ghosts([u"d"]))
297
self.assertEqual([u"a", u"e", u"b"],
298
index.get_ancestry_with_ghosts([u"a", u"b"]))
299
self.assertEqual([u"a", u"g", u"f", u"c"],
300
index.get_ancestry_with_ghosts([u"a", u"c"]))
302
[u"a", u"g", u"f", u"c", u"e", u"b", u"k", u"j", u"h", u"d"],
303
index.get_ancestry_with_ghosts([u"b", u"d"]))
305
self.assertRaises(RevisionNotPresent,
306
index.get_ancestry_with_ghosts, [u"e"])
308
def test_num_versions(self):
309
transport = MockTransport([
312
index = _KnitIndex(transport, "filename", "r")
314
self.assertEqual(0, index.num_versions())
315
self.assertEqual(0, len(index))
317
index.add_version(u"a", ["option"], 0, 1, [])
318
self.assertEqual(1, index.num_versions())
319
self.assertEqual(1, len(index))
321
index.add_version(u"a", ["option2"], 1, 2, [])
322
self.assertEqual(1, index.num_versions())
323
self.assertEqual(1, len(index))
325
index.add_version(u"b", ["option"], 0, 1, [])
326
self.assertEqual(2, index.num_versions())
327
self.assertEqual(2, len(index))
329
def test_get_versions(self):
330
transport = MockTransport([
333
index = _KnitIndex(transport, "filename", "r")
335
self.assertEqual([], index.get_versions())
337
index.add_version(u"a", ["option"], 0, 1, [])
338
self.assertEqual([u"a"], index.get_versions())
340
index.add_version(u"a", ["option"], 0, 1, [])
341
self.assertEqual([u"a"], index.get_versions())
343
index.add_version(u"b", ["option"], 0, 1, [])
344
self.assertEqual([u"a", u"b"], index.get_versions())
346
def test_idx_to_name(self):
347
transport = MockTransport([
352
index = _KnitIndex(transport, "filename", "r")
354
self.assertEqual(u"a", index.idx_to_name(0))
355
self.assertEqual(u"b", index.idx_to_name(1))
356
self.assertEqual(u"b", index.idx_to_name(-1))
357
self.assertEqual(u"a", index.idx_to_name(-2))
359
def test_lookup(self):
360
transport = MockTransport([
365
index = _KnitIndex(transport, "filename", "r")
367
self.assertEqual(0, index.lookup(u"a"))
368
self.assertEqual(1, index.lookup(u"b"))
370
def test_add_version(self):
371
transport = MockTransport([
374
index = _KnitIndex(transport, "filename", "r")
376
index.add_version(u"a", ["option"], 0, 1, [u"b"])
377
self.assertEqual(("append_bytes",
378
("filename", "\na option 0 1 .b :"),
379
{}), transport.calls.pop(0))
380
self.assertTrue(index.has_version(u"a"))
381
self.assertEqual(1, index.num_versions())
382
self.assertEqual((0, 1), index.get_position(u"a"))
383
self.assertEqual(["option"], index.get_options(u"a"))
384
self.assertEqual([u"b"], index.get_parents_with_ghosts(u"a"))
386
index.add_version(u"a", ["opt"], 1, 2, [u"c"])
387
self.assertEqual(("append_bytes",
388
("filename", "\na opt 1 2 .c :"),
389
{}), transport.calls.pop(0))
390
self.assertTrue(index.has_version(u"a"))
391
self.assertEqual(1, index.num_versions())
392
self.assertEqual((1, 2), index.get_position(u"a"))
393
self.assertEqual(["opt"], index.get_options(u"a"))
394
self.assertEqual([u"c"], index.get_parents_with_ghosts(u"a"))
396
index.add_version(u"b", ["option"], 2, 3, [u"a"])
397
self.assertEqual(("append_bytes",
398
("filename", "\nb option 2 3 0 :"),
399
{}), transport.calls.pop(0))
400
self.assertTrue(index.has_version(u"b"))
401
self.assertEqual(2, index.num_versions())
402
self.assertEqual((2, 3), index.get_position(u"b"))
403
self.assertEqual(["option"], index.get_options(u"b"))
404
self.assertEqual([u"a"], index.get_parents_with_ghosts(u"b"))
663
406
def test_add_versions(self):
664
407
transport = MockTransport([
667
index = self.get_knit_index(transport, "filename", "r")
410
index = _KnitIndex(transport, "filename", "r")
670
call = transport.calls.pop(0)
671
# call[1][1] is a StringIO - we can't test it by simple equality.
672
self.assertEqual('put_file_non_atomic', call[0])
673
self.assertEqual('filename.kndx', call[1][0])
674
# With no history, _KndxIndex writes a new index:
413
(u"a", ["option"], 0, 1, [u"b"]),
414
(u"a", ["opt"], 1, 2, [u"c"]),
415
(u"b", ["option"], 2, 3, [u"a"])
417
self.assertEqual(("append_bytes", ("filename",
677
418
"\na option 0 1 .b :"
678
419
"\na opt 1 2 .c :"
679
"\nb option 2 3 0 :",
680
call[1][1].getvalue())
681
self.assertEqual({'create_parent_dir': True}, call[2])
682
self.assertIndexIsAB(index)
684
def test_add_versions_random_id_is_accepted(self):
685
transport = MockTransport([
688
index = self.get_knit_index(transport, "filename", "r")
689
self.add_a_b(index, random_id=True)
421
), {}), transport.calls.pop(0))
422
self.assertTrue(index.has_version(u"a"))
423
self.assertTrue(index.has_version(u"b"))
424
self.assertEqual(2, index.num_versions())
425
self.assertEqual((1, 2), index.get_position(u"a"))
426
self.assertEqual((2, 3), index.get_position(u"b"))
427
self.assertEqual(["opt"], index.get_options(u"a"))
428
self.assertEqual(["option"], index.get_options(u"b"))
429
self.assertEqual([u"c"], index.get_parents_with_ghosts(u"a"))
430
self.assertEqual([u"a"], index.get_parents_with_ghosts(u"b"))
691
432
def test_delay_create_and_add_versions(self):
692
433
transport = MockTransport()
694
index = self.get_knit_index(transport, "filename", "w")
435
index = _KnitIndex(transport, "filename", "w",
436
create=True, file_mode="wb", create_parent_dir=True,
437
delay_create=True, dir_mode=0777)
696
438
self.assertEqual([], transport.calls)
699
#[ {"dir_mode": 0777, "create_parent_dir": True, "mode": "wb"},
701
# Two calls: one during which we load the existing index (and when its
702
# missing create it), then a second where we write the contents out.
703
self.assertEqual(2, len(transport.calls))
704
call = transport.calls.pop(0)
705
self.assertEqual('put_file_non_atomic', call[0])
706
self.assertEqual('filename.kndx', call[1][0])
707
# With no history, _KndxIndex writes a new index:
708
self.assertEqual(_KndxIndex.HEADER, call[1][1].getvalue())
709
self.assertEqual({'create_parent_dir': True}, call[2])
710
call = transport.calls.pop(0)
711
# call[1][1] is a StringIO - we can't test it by simple equality.
712
self.assertEqual('put_file_non_atomic', call[0])
713
self.assertEqual('filename.kndx', call[1][0])
714
# With no history, _KndxIndex writes a new index:
441
(u"a", ["option"], 0, 1, [u"b"]),
442
(u"a", ["opt"], 1, 2, [u"c"]),
443
(u"b", ["option"], 2, 3, [u"a"])
445
name, (filename, f), kwargs = transport.calls.pop(0)
446
self.assertEqual("put_file_non_atomic", name)
448
{"dir_mode": 0777, "create_parent_dir": True, "mode": "wb"},
450
self.assertEqual("filename", filename)
717
453
"\na option 0 1 .b :"
718
454
"\na opt 1 2 .c :"
719
455
"\nb option 2 3 0 :",
720
call[1][1].getvalue())
721
self.assertEqual({'create_parent_dir': True}, call[2])
458
def test_has_version(self):
459
transport = MockTransport([
463
index = _KnitIndex(transport, "filename", "r")
465
self.assertTrue(index.has_version(u"a"))
466
self.assertFalse(index.has_version(u"b"))
723
468
def test_get_position(self):
724
469
transport = MockTransport([
726
471
"a option 0 1 :",
729
index = self.get_knit_index(transport, "filename", "r")
474
index = _KnitIndex(transport, "filename", "r")
731
self.assertEqual((("a",), 0, 1), index.get_position(("a",)))
732
self.assertEqual((("b",), 1, 2), index.get_position(("b",)))
476
self.assertEqual((0, 1), index.get_position(u"a"))
477
self.assertEqual((1, 2), index.get_position(u"b"))
734
479
def test_get_method(self):
735
480
transport = MockTransport([
737
482
"a fulltext,unknown 0 1 :",
738
483
"b unknown,line-delta 1 2 :",
741
index = self.get_knit_index(transport, "filename", "r")
486
index = _KnitIndex(transport, "filename", "r")
743
self.assertEqual("fulltext", index.get_method("a"))
744
self.assertEqual("line-delta", index.get_method("b"))
745
self.assertRaises(errors.KnitIndexUnknownMethod, index.get_method, "c")
488
self.assertEqual("fulltext", index.get_method(u"a"))
489
self.assertEqual("line-delta", index.get_method(u"b"))
490
self.assertRaises(errors.KnitIndexUnknownMethod, index.get_method, u"c")
747
492
def test_get_options(self):
748
493
transport = MockTransport([
751
496
"b opt2,opt3 1 2 :"
753
index = self.get_knit_index(transport, "filename", "r")
755
self.assertEqual(["opt1"], index.get_options("a"))
756
self.assertEqual(["opt2", "opt3"], index.get_options("b"))
758
def test_get_parent_map(self):
759
transport = MockTransport([
762
"b option 1 2 0 .c :",
763
"c option 1 2 1 0 .e :"
765
index = self.get_knit_index(transport, "filename", "r")
769
("b",):(("a",), ("c",)),
770
("c",):(("b",), ("a",), ("e",)),
771
}, index.get_parent_map(index.keys()))
773
def test_impossible_parent(self):
774
"""Test we get KnitCorrupt if the parent couldn't possibly exist."""
775
transport = MockTransport([
778
"b option 0 1 4 :" # We don't have a 4th record
780
index = self.get_knit_index(transport, 'filename', 'r')
782
self.assertRaises(errors.KnitCorrupt, index.keys)
784
if (str(e) == ('exceptions must be strings, classes, or instances,'
785
' not exceptions.IndexError')
786
and sys.version_info[0:2] >= (2,5)):
787
self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
788
' raising new style exceptions with python'
793
def test_corrupted_parent(self):
794
transport = MockTransport([
798
"c option 0 1 1v :", # Can't have a parent of '1v'
800
index = self.get_knit_index(transport, 'filename', 'r')
802
self.assertRaises(errors.KnitCorrupt, index.keys)
804
if (str(e) == ('exceptions must be strings, classes, or instances,'
805
' not exceptions.ValueError')
806
and sys.version_info[0:2] >= (2,5)):
807
self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
808
' raising new style exceptions with python'
813
def test_corrupted_parent_in_list(self):
814
transport = MockTransport([
818
"c option 0 1 1 v :", # Can't have a parent of 'v'
820
index = self.get_knit_index(transport, 'filename', 'r')
822
self.assertRaises(errors.KnitCorrupt, index.keys)
824
if (str(e) == ('exceptions must be strings, classes, or instances,'
825
' not exceptions.ValueError')
826
and sys.version_info[0:2] >= (2,5)):
827
self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
828
' raising new style exceptions with python'
833
def test_invalid_position(self):
834
transport = MockTransport([
838
index = self.get_knit_index(transport, 'filename', 'r')
840
self.assertRaises(errors.KnitCorrupt, index.keys)
842
if (str(e) == ('exceptions must be strings, classes, or instances,'
843
' not exceptions.ValueError')
844
and sys.version_info[0:2] >= (2,5)):
845
self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
846
' raising new style exceptions with python'
851
def test_invalid_size(self):
852
transport = MockTransport([
856
index = self.get_knit_index(transport, 'filename', 'r')
858
self.assertRaises(errors.KnitCorrupt, index.keys)
860
if (str(e) == ('exceptions must be strings, classes, or instances,'
861
' not exceptions.ValueError')
862
and sys.version_info[0:2] >= (2,5)):
863
self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
864
' raising new style exceptions with python'
869
def test_short_line(self):
870
transport = MockTransport([
873
"b option 10 10 0", # This line isn't terminated, ignored
875
index = self.get_knit_index(transport, "filename", "r")
876
self.assertEqual(set([('a',)]), index.keys())
878
def test_skip_incomplete_record(self):
879
# A line with bogus data should just be skipped
880
transport = MockTransport([
883
"b option 10 10 0", # This line isn't terminated, ignored
884
"c option 20 10 0 :", # Properly terminated, and starts with '\n'
886
index = self.get_knit_index(transport, "filename", "r")
887
self.assertEqual(set([('a',), ('c',)]), index.keys())
889
def test_trailing_characters(self):
890
# A line with bogus data should just be skipped
891
transport = MockTransport([
894
"b option 10 10 0 :a", # This line has extra trailing characters
895
"c option 20 10 0 :", # Properly terminated, and starts with '\n'
897
index = self.get_knit_index(transport, "filename", "r")
898
self.assertEqual(set([('a',), ('c',)]), index.keys())
901
class LowLevelKnitIndexTests_c(LowLevelKnitIndexTests):
903
_test_needs_features = [CompiledKnitFeature]
905
def get_knit_index(self, transport, name, mode):
906
mapper = ConstantMapper(name)
907
orig = knit._load_data
909
knit._load_data = orig
910
self.addCleanup(reset)
911
from bzrlib._knit_load_data_c import _load_data_c
912
knit._load_data = _load_data_c
913
allow_writes = lambda: mode == 'w'
914
return _KndxIndex(transport, mapper, lambda:None, allow_writes, lambda:True)
498
index = _KnitIndex(transport, "filename", "r")
500
self.assertEqual(["opt1"], index.get_options(u"a"))
501
self.assertEqual(["opt2", "opt3"], index.get_options(u"b"))
503
def test_get_parents(self):
504
transport = MockTransport([
507
"b option 1 2 0 .c :",
508
"c option 1 2 1 0 .e :"
510
index = _KnitIndex(transport, "filename", "r")
512
self.assertEqual([], index.get_parents(u"a"))
513
self.assertEqual([u"a", u"c"], index.get_parents(u"b"))
514
self.assertEqual([u"b", u"a"], index.get_parents(u"c"))
516
def test_get_parents_with_ghosts(self):
517
transport = MockTransport([
520
"b option 1 2 0 .c :",
521
"c option 1 2 1 0 .e :"
523
index = _KnitIndex(transport, "filename", "r")
525
self.assertEqual([], index.get_parents_with_ghosts(u"a"))
526
self.assertEqual([u"a", u"c"], index.get_parents_with_ghosts(u"b"))
527
self.assertEqual([u"b", u"a", u"e"],
528
index.get_parents_with_ghosts(u"c"))
530
def test_check_versions_present(self):
531
transport = MockTransport([
536
index = _KnitIndex(transport, "filename", "r")
538
check = index.check_versions_present
544
self.assertRaises(RevisionNotPresent, check, [u"c"])
545
self.assertRaises(RevisionNotPresent, check, [u"a", u"b", u"c"])
917
548
class KnitTests(TestCaseWithTransport):
918
549
"""Class containing knit test helper routines."""
920
def make_test_knit(self, annotate=False, name='test'):
921
mapper = ConstantMapper(name)
922
return make_file_factory(annotate, mapper)(self.get_transport())
551
def make_test_knit(self, annotate=False, delay_create=False):
553
factory = KnitPlainFactory()
556
return KnitVersionedFile('test', get_transport('.'), access_mode='w',
557
factory=factory, create=True,
558
delay_create=delay_create)
561
class BasicKnitTests(KnitTests):
563
def add_stock_one_and_one_a(self, k):
564
k.add_lines('text-1', [], split_lines(TEXT_1))
565
k.add_lines('text-1a', ['text-1'], split_lines(TEXT_1A))
567
def test_knit_constructor(self):
568
"""Construct empty k"""
569
self.make_test_knit()
571
def test_knit_add(self):
572
"""Store one text in knit and retrieve"""
573
k = self.make_test_knit()
574
k.add_lines('text-1', [], split_lines(TEXT_1))
575
self.assertTrue(k.has_version('text-1'))
576
self.assertEqualDiff(''.join(k.get_lines('text-1')), TEXT_1)
578
def test_knit_reload(self):
579
# test that the content in a reloaded knit is correct
580
k = self.make_test_knit()
581
k.add_lines('text-1', [], split_lines(TEXT_1))
583
k2 = KnitVersionedFile('test', get_transport('.'), access_mode='r', factory=KnitPlainFactory(), create=True)
584
self.assertTrue(k2.has_version('text-1'))
585
self.assertEqualDiff(''.join(k2.get_lines('text-1')), TEXT_1)
587
def test_knit_several(self):
588
"""Store several texts in a knit"""
589
k = self.make_test_knit()
590
k.add_lines('text-1', [], split_lines(TEXT_1))
591
k.add_lines('text-2', [], split_lines(TEXT_2))
592
self.assertEqualDiff(''.join(k.get_lines('text-1')), TEXT_1)
593
self.assertEqualDiff(''.join(k.get_lines('text-2')), TEXT_2)
595
def test_repeated_add(self):
596
"""Knit traps attempt to replace existing version"""
597
k = self.make_test_knit()
598
k.add_lines('text-1', [], split_lines(TEXT_1))
599
self.assertRaises(RevisionAlreadyPresent,
601
'text-1', [], split_lines(TEXT_1))
603
def test_empty(self):
604
k = self.make_test_knit(True)
605
k.add_lines('text-1', [], [])
606
self.assertEquals(k.get_lines('text-1'), [])
608
def test_incomplete(self):
609
"""Test if texts without a ending line-end can be inserted and
611
k = KnitVersionedFile('test', get_transport('.'), delta=False, create=True)
612
k.add_lines('text-1', [], ['a\n', 'b' ])
613
k.add_lines('text-2', ['text-1'], ['a\rb\n', 'b\n'])
614
# reopening ensures maximum room for confusion
615
k = KnitVersionedFile('test', get_transport('.'), delta=False, create=True)
616
self.assertEquals(k.get_lines('text-1'), ['a\n', 'b' ])
617
self.assertEquals(k.get_lines('text-2'), ['a\rb\n', 'b\n'])
619
def test_delta(self):
620
"""Expression of knit delta as lines"""
621
k = self.make_test_knit()
622
td = list(line_delta(TEXT_1.splitlines(True),
623
TEXT_1A.splitlines(True)))
624
self.assertEqualDiff(''.join(td), delta_1_1a)
625
out = apply_line_delta(TEXT_1.splitlines(True), td)
626
self.assertEqualDiff(''.join(out), TEXT_1A)
628
def test_add_with_parents(self):
629
"""Store in knit with parents"""
630
k = self.make_test_knit()
631
self.add_stock_one_and_one_a(k)
632
self.assertEquals(k.get_parents('text-1'), [])
633
self.assertEquals(k.get_parents('text-1a'), ['text-1'])
635
def test_ancestry(self):
636
"""Store in knit with parents"""
637
k = self.make_test_knit()
638
self.add_stock_one_and_one_a(k)
639
self.assertEquals(set(k.get_ancestry(['text-1a'])), set(['text-1a', 'text-1']))
641
def test_add_delta(self):
642
"""Store in knit with parents"""
643
k = KnitVersionedFile('test', get_transport('.'), factory=KnitPlainFactory(),
644
delta=True, create=True)
645
self.add_stock_one_and_one_a(k)
647
self.assertEqualDiff(''.join(k.get_lines('text-1a')), TEXT_1A)
649
def test_annotate(self):
651
k = KnitVersionedFile('knit', get_transport('.'), factory=KnitAnnotateFactory(),
652
delta=True, create=True)
653
self.insert_and_test_small_annotate(k)
655
def insert_and_test_small_annotate(self, k):
656
"""test annotation with k works correctly."""
657
k.add_lines('text-1', [], ['a\n', 'b\n'])
658
k.add_lines('text-2', ['text-1'], ['a\n', 'c\n'])
660
origins = k.annotate('text-2')
661
self.assertEquals(origins[0], ('text-1', 'a\n'))
662
self.assertEquals(origins[1], ('text-2', 'c\n'))
664
def test_annotate_fulltext(self):
666
k = KnitVersionedFile('knit', get_transport('.'), factory=KnitAnnotateFactory(),
667
delta=False, create=True)
668
self.insert_and_test_small_annotate(k)
670
def test_annotate_merge_1(self):
671
k = self.make_test_knit(True)
672
k.add_lines('text-a1', [], ['a\n', 'b\n'])
673
k.add_lines('text-a2', [], ['d\n', 'c\n'])
674
k.add_lines('text-am', ['text-a1', 'text-a2'], ['d\n', 'b\n'])
675
origins = k.annotate('text-am')
676
self.assertEquals(origins[0], ('text-a2', 'd\n'))
677
self.assertEquals(origins[1], ('text-a1', 'b\n'))
679
def test_annotate_merge_2(self):
680
k = self.make_test_knit(True)
681
k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
682
k.add_lines('text-a2', [], ['x\n', 'y\n', 'z\n'])
683
k.add_lines('text-am', ['text-a1', 'text-a2'], ['a\n', 'y\n', 'c\n'])
684
origins = k.annotate('text-am')
685
self.assertEquals(origins[0], ('text-a1', 'a\n'))
686
self.assertEquals(origins[1], ('text-a2', 'y\n'))
687
self.assertEquals(origins[2], ('text-a1', 'c\n'))
689
def test_annotate_merge_9(self):
690
k = self.make_test_knit(True)
691
k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
692
k.add_lines('text-a2', [], ['x\n', 'y\n', 'z\n'])
693
k.add_lines('text-am', ['text-a1', 'text-a2'], ['k\n', 'y\n', 'c\n'])
694
origins = k.annotate('text-am')
695
self.assertEquals(origins[0], ('text-am', 'k\n'))
696
self.assertEquals(origins[1], ('text-a2', 'y\n'))
697
self.assertEquals(origins[2], ('text-a1', 'c\n'))
699
def test_annotate_merge_3(self):
700
k = self.make_test_knit(True)
701
k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
702
k.add_lines('text-a2', [] ,['x\n', 'y\n', 'z\n'])
703
k.add_lines('text-am', ['text-a1', 'text-a2'], ['k\n', 'y\n', 'z\n'])
704
origins = k.annotate('text-am')
705
self.assertEquals(origins[0], ('text-am', 'k\n'))
706
self.assertEquals(origins[1], ('text-a2', 'y\n'))
707
self.assertEquals(origins[2], ('text-a2', 'z\n'))
709
def test_annotate_merge_4(self):
710
k = self.make_test_knit(True)
711
k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
712
k.add_lines('text-a2', [], ['x\n', 'y\n', 'z\n'])
713
k.add_lines('text-a3', ['text-a1'], ['a\n', 'b\n', 'p\n'])
714
k.add_lines('text-am', ['text-a2', 'text-a3'], ['a\n', 'b\n', 'z\n'])
715
origins = k.annotate('text-am')
716
self.assertEquals(origins[0], ('text-a1', 'a\n'))
717
self.assertEquals(origins[1], ('text-a1', 'b\n'))
718
self.assertEquals(origins[2], ('text-a2', 'z\n'))
720
def test_annotate_merge_5(self):
721
k = self.make_test_knit(True)
722
k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
723
k.add_lines('text-a2', [], ['d\n', 'e\n', 'f\n'])
724
k.add_lines('text-a3', [], ['x\n', 'y\n', 'z\n'])
725
k.add_lines('text-am',
726
['text-a1', 'text-a2', 'text-a3'],
727
['a\n', 'e\n', 'z\n'])
728
origins = k.annotate('text-am')
729
self.assertEquals(origins[0], ('text-a1', 'a\n'))
730
self.assertEquals(origins[1], ('text-a2', 'e\n'))
731
self.assertEquals(origins[2], ('text-a3', 'z\n'))
733
def test_annotate_file_cherry_pick(self):
734
k = self.make_test_knit(True)
735
k.add_lines('text-1', [], ['a\n', 'b\n', 'c\n'])
736
k.add_lines('text-2', ['text-1'], ['d\n', 'e\n', 'f\n'])
737
k.add_lines('text-3', ['text-2', 'text-1'], ['a\n', 'b\n', 'c\n'])
738
origins = k.annotate('text-3')
739
self.assertEquals(origins[0], ('text-1', 'a\n'))
740
self.assertEquals(origins[1], ('text-1', 'b\n'))
741
self.assertEquals(origins[2], ('text-1', 'c\n'))
743
def test_knit_join(self):
744
"""Store in knit with parents"""
745
k1 = KnitVersionedFile('test1', get_transport('.'), factory=KnitPlainFactory(), create=True)
746
k1.add_lines('text-a', [], split_lines(TEXT_1))
747
k1.add_lines('text-b', ['text-a'], split_lines(TEXT_1))
749
k1.add_lines('text-c', [], split_lines(TEXT_1))
750
k1.add_lines('text-d', ['text-c'], split_lines(TEXT_1))
752
k1.add_lines('text-m', ['text-b', 'text-d'], split_lines(TEXT_1))
754
k2 = KnitVersionedFile('test2', get_transport('.'), factory=KnitPlainFactory(), create=True)
755
count = k2.join(k1, version_ids=['text-m'])
756
self.assertEquals(count, 5)
757
self.assertTrue(k2.has_version('text-a'))
758
self.assertTrue(k2.has_version('text-c'))
760
def test_reannotate(self):
761
k1 = KnitVersionedFile('knit1', get_transport('.'),
762
factory=KnitAnnotateFactory(), create=True)
764
k1.add_lines('text-a', [], ['a\n', 'b\n'])
766
k1.add_lines('text-b', ['text-a'], ['a\n', 'c\n'])
768
k2 = KnitVersionedFile('test2', get_transport('.'),
769
factory=KnitAnnotateFactory(), create=True)
770
k2.join(k1, version_ids=['text-b'])
773
k1.add_lines('text-X', ['text-b'], ['a\n', 'b\n'])
775
k2.add_lines('text-c', ['text-b'], ['z\n', 'c\n'])
777
k2.add_lines('text-Y', ['text-b'], ['b\n', 'c\n'])
779
# test-c will have index 3
780
k1.join(k2, version_ids=['text-c'])
782
lines = k1.get_lines('text-c')
783
self.assertEquals(lines, ['z\n', 'c\n'])
785
origins = k1.annotate('text-c')
786
self.assertEquals(origins[0], ('text-c', 'z\n'))
787
self.assertEquals(origins[1], ('text-b', 'c\n'))
789
def test_get_line_delta_texts(self):
790
"""Make sure we can call get_texts on text with reused line deltas"""
791
k1 = KnitVersionedFile('test1', get_transport('.'),
792
factory=KnitPlainFactory(), create=True)
797
parents = ['%d' % (t-1)]
798
k1.add_lines('%d' % t, parents, ['hello\n'] * t)
799
k1.get_texts(('%d' % t) for t in range(3))
801
def test_iter_lines_reads_in_order(self):
802
t = MemoryTransport()
803
instrumented_t = TransportLogger(t)
804
k1 = KnitVersionedFile('id', instrumented_t, create=True, delta=True)
805
self.assertEqual([('id.kndx',)], instrumented_t._calls)
806
# add texts with no required ordering
807
k1.add_lines('base', [], ['text\n'])
808
k1.add_lines('base2', [], ['text2\n'])
810
instrumented_t._calls = []
811
# request a last-first iteration
812
results = list(k1.iter_lines_added_or_present_in_versions(['base2', 'base']))
813
self.assertEqual([('id.knit', [(0, 87), (87, 89)])], instrumented_t._calls)
814
self.assertEqual(['text\n', 'text2\n'], results)
816
def test_create_empty_annotated(self):
817
k1 = self.make_test_knit(True)
819
k1.add_lines('text-a', [], ['a\n', 'b\n'])
820
k2 = k1.create_empty('t', MemoryTransport())
821
self.assertTrue(isinstance(k2.factory, KnitAnnotateFactory))
822
self.assertEqual(k1.delta, k2.delta)
823
# the generic test checks for empty content and file class
825
def test_knit_format(self):
826
# this tests that a new knit index file has the expected content
827
# and that is writes the data we expect as records are added.
828
knit = self.make_test_knit(True)
829
# Now knit files are not created until we first add data to them
830
self.assertFileEqual("# bzr knit index 8\n", 'test.kndx')
831
knit.add_lines_with_ghosts('revid', ['a_ghost'], ['a\n'])
832
self.assertFileEqual(
833
"# bzr knit index 8\n"
835
"revid fulltext 0 84 .a_ghost :",
837
knit.add_lines_with_ghosts('revid2', ['revid'], ['a\n'])
838
self.assertFileEqual(
839
"# bzr knit index 8\n"
840
"\nrevid fulltext 0 84 .a_ghost :"
841
"\nrevid2 line-delta 84 82 0 :",
843
# we should be able to load this file again
844
knit = KnitVersionedFile('test', get_transport('.'), access_mode='r')
845
self.assertEqual(['revid', 'revid2'], knit.versions())
846
# write a short write to the file and ensure that its ignored
847
indexfile = file('test.kndx', 'at')
848
indexfile.write('\nrevid3 line-delta 166 82 1 2 3 4 5 .phwoar:demo ')
850
# we should be able to load this file again
851
knit = KnitVersionedFile('test', get_transport('.'), access_mode='w')
852
self.assertEqual(['revid', 'revid2'], knit.versions())
853
# and add a revision with the same id the failed write had
854
knit.add_lines('revid3', ['revid2'], ['a\n'])
855
# and when reading it revid3 should now appear.
856
knit = KnitVersionedFile('test', get_transport('.'), access_mode='r')
857
self.assertEqual(['revid', 'revid2', 'revid3'], knit.versions())
858
self.assertEqual(['revid2'], knit.get_parents('revid3'))
860
def test_delay_create(self):
861
"""Test that passing delay_create=True creates files late"""
862
knit = self.make_test_knit(annotate=True, delay_create=True)
863
self.failIfExists('test.knit')
864
self.failIfExists('test.kndx')
865
knit.add_lines_with_ghosts('revid', ['a_ghost'], ['a\n'])
866
self.failUnlessExists('test.knit')
867
self.assertFileEqual(
868
"# bzr knit index 8\n"
870
"revid fulltext 0 84 .a_ghost :",
873
def test_create_parent_dir(self):
874
"""create_parent_dir can create knits in nonexistant dirs"""
875
# Has no effect if we don't set 'delay_create'
876
trans = get_transport('.')
877
self.assertRaises(NoSuchFile, KnitVersionedFile, 'dir/test',
878
trans, access_mode='w', factory=None,
879
create=True, create_parent_dir=True)
880
# Nothing should have changed yet
881
knit = KnitVersionedFile('dir/test', trans, access_mode='w',
882
factory=None, create=True,
883
create_parent_dir=True,
885
self.failIfExists('dir/test.knit')
886
self.failIfExists('dir/test.kndx')
887
self.failIfExists('dir')
888
knit.add_lines('revid', [], ['a\n'])
889
self.failUnlessExists('dir')
890
self.failUnlessExists('dir/test.knit')
891
self.assertFileEqual(
892
"# bzr knit index 8\n"
894
"revid fulltext 0 84 :",
897
def test_create_mode_700(self):
898
trans = get_transport('.')
899
if not trans._can_roundtrip_unix_modebits():
900
# Can't roundtrip, so no need to run this test
902
knit = KnitVersionedFile('dir/test', trans, access_mode='w',
903
factory=None, create=True,
904
create_parent_dir=True,
908
knit.add_lines('revid', [], ['a\n'])
909
self.assertTransportMode(trans, 'dir', 0700)
910
self.assertTransportMode(trans, 'dir/test.knit', 0600)
911
self.assertTransportMode(trans, 'dir/test.kndx', 0600)
913
def test_create_mode_770(self):
914
trans = get_transport('.')
915
if not trans._can_roundtrip_unix_modebits():
916
# Can't roundtrip, so no need to run this test
918
knit = KnitVersionedFile('dir/test', trans, access_mode='w',
919
factory=None, create=True,
920
create_parent_dir=True,
924
knit.add_lines('revid', [], ['a\n'])
925
self.assertTransportMode(trans, 'dir', 0770)
926
self.assertTransportMode(trans, 'dir/test.knit', 0660)
927
self.assertTransportMode(trans, 'dir/test.kndx', 0660)
929
def test_create_mode_777(self):
930
trans = get_transport('.')
931
if not trans._can_roundtrip_unix_modebits():
932
# Can't roundtrip, so no need to run this test
934
knit = KnitVersionedFile('dir/test', trans, access_mode='w',
935
factory=None, create=True,
936
create_parent_dir=True,
940
knit.add_lines('revid', [], ['a\n'])
941
self.assertTransportMode(trans, 'dir', 0777)
942
self.assertTransportMode(trans, 'dir/test.knit', 0666)
943
self.assertTransportMode(trans, 'dir/test.kndx', 0666)
945
def test_plan_merge(self):
946
my_knit = self.make_test_knit(annotate=True)
947
my_knit.add_lines('text1', [], split_lines(TEXT_1))
948
my_knit.add_lines('text1a', ['text1'], split_lines(TEXT_1A))
949
my_knit.add_lines('text1b', ['text1'], split_lines(TEXT_1B))
950
plan = list(my_knit.plan_merge('text1a', 'text1b'))
951
for plan_line, expected_line in zip(plan, AB_MERGE):
952
self.assertEqual(plan_line, expected_line)
964
Banana cup cake recipe
974
Banana cup cake recipe
976
- bananas (do not use plantains!!!)
983
Banana cup cake recipe
999
AB_MERGE_TEXT="""unchanged|Banana cup cake recipe
1004
new-b|- bananas (do not use plantains!!!)
1005
unchanged|- broken tea cups
1006
new-a|- self-raising flour
1009
AB_MERGE=[tuple(l.split('|')) for l in AB_MERGE_TEXT.splitlines(True)]
1012
def line_delta(from_lines, to_lines):
1013
"""Generate line-based delta from one text to another"""
1014
s = difflib.SequenceMatcher(None, from_lines, to_lines)
1015
for op in s.get_opcodes():
1016
if op[0] == 'equal':
1018
yield '%d,%d,%d\n' % (op[1], op[2], op[4]-op[3])
1019
for i in range(op[3], op[4]):
1023
def apply_line_delta(basis_lines, delta_lines):
1024
"""Apply a line-based perfect diff
1026
basis_lines -- text to apply the patch to
1027
delta_lines -- diff instructions and content
1029
out = basis_lines[:]
1032
while i < len(delta_lines):
1034
a, b, c = map(long, l.split(','))
1036
out[offset+a:offset+b] = delta_lines[i:i+c]
1038
offset = offset + (b - a) + c
1042
class TestWeaveToKnit(KnitTests):
1044
def test_weave_to_knit_matches(self):
1045
# check that the WeaveToKnit is_compatible function
1046
# registers True for a Weave to a Knit.
1048
k = self.make_test_knit()
1049
self.failUnless(WeaveToKnit.is_compatible(w, k))
1050
self.failIf(WeaveToKnit.is_compatible(k, w))
1051
self.failIf(WeaveToKnit.is_compatible(w, w))
1052
self.failIf(WeaveToKnit.is_compatible(k, k))
1055
class TestKnitCaching(KnitTests):
1057
def create_knit(self, cache_add=False):
1058
k = self.make_test_knit(True)
1062
k.add_lines('text-1', [], split_lines(TEXT_1))
1063
k.add_lines('text-2', [], split_lines(TEXT_2))
1066
def test_no_caching(self):
1067
k = self.create_knit()
1068
# Nothing should be cached without setting 'enable_cache'
1069
self.assertEqual({}, k._data._cache)
1071
def test_cache_add_and_clear(self):
1072
k = self.create_knit(True)
1074
self.assertEqual(['text-1', 'text-2'], sorted(k._data._cache.keys()))
1077
self.assertEqual({}, k._data._cache)
1079
def test_cache_data_read_raw(self):
1080
k = self.create_knit()
1082
# Now cache and read
1085
def read_one_raw(version):
1086
pos_map = k._get_components_positions([version])
1087
method, pos, size, next = pos_map[version]
1088
lst = list(k._data.read_records_iter_raw([(version, pos, size)]))
1089
self.assertEqual(1, len(lst))
1092
val = read_one_raw('text-1')
1093
self.assertEqual({'text-1':val[1]}, k._data._cache)
1096
# After clear, new reads are not cached
1097
self.assertEqual({}, k._data._cache)
1099
val2 = read_one_raw('text-1')
1100
self.assertEqual(val, val2)
1101
self.assertEqual({}, k._data._cache)
1103
def test_cache_data_read(self):
1104
k = self.create_knit()
1106
def read_one(version):
1107
pos_map = k._get_components_positions([version])
1108
method, pos, size, next = pos_map[version]
1109
lst = list(k._data.read_records_iter([(version, pos, size)]))
1110
self.assertEqual(1, len(lst))
1113
# Now cache and read
1116
val = read_one('text-2')
1117
self.assertEqual(['text-2'], k._data._cache.keys())
1118
self.assertEqual('text-2', val[0])
1119
content, digest = k._data._parse_record('text-2',
1120
k._data._cache['text-2'])
1121
self.assertEqual(content, val[1])
1122
self.assertEqual(digest, val[2])
1125
self.assertEqual({}, k._data._cache)
1127
val2 = read_one('text-2')
1128
self.assertEqual(val, val2)
1129
self.assertEqual({}, k._data._cache)
1131
def test_cache_read(self):
1132
k = self.create_knit()
1135
text = k.get_text('text-1')
1136
self.assertEqual(TEXT_1, text)
1137
self.assertEqual(['text-1'], k._data._cache.keys())
1140
self.assertEqual({}, k._data._cache)
1142
text = k.get_text('text-1')
1143
self.assertEqual(TEXT_1, text)
1144
self.assertEqual({}, k._data._cache)
925
1147
class TestKnitIndex(KnitTests):
1007
1220
def test_knit_index_checks_header(self):
1008
1221
t = get_transport('.')
1009
1222
t.put_bytes('test.kndx', '# not really a knit header\n\n')
1010
k = self.make_test_knit()
1011
self.assertRaises(KnitHeaderError, k.keys)
1014
class TestGraphIndexKnit(KnitTests):
1015
"""Tests for knits using a GraphIndex rather than a KnitIndex."""
1017
def make_g_index(self, name, ref_lists=0, nodes=[]):
1018
builder = GraphIndexBuilder(ref_lists)
1019
for node, references, value in nodes:
1020
builder.add_node(node, references, value)
1021
stream = builder.finish()
1022
trans = self.get_transport()
1023
size = trans.put_file(name, stream)
1024
return GraphIndex(trans, name, size)
1026
def two_graph_index(self, deltas=False, catch_adds=False):
1027
"""Build a two-graph index.
1029
:param deltas: If true, use underlying indices with two node-ref
1030
lists and 'parent' set to a delta-compressed against tail.
1032
# build a complex graph across several indices.
1034
# delta compression inn the index
1035
index1 = self.make_g_index('1', 2, [
1036
(('tip', ), 'N0 100', ([('parent', )], [], )),
1037
(('tail', ), '', ([], []))])
1038
index2 = self.make_g_index('2', 2, [
1039
(('parent', ), ' 100 78', ([('tail', ), ('ghost', )], [('tail', )])),
1040
(('separate', ), '', ([], []))])
1042
# just blob location and graph in the index.
1043
index1 = self.make_g_index('1', 1, [
1044
(('tip', ), 'N0 100', ([('parent', )], )),
1045
(('tail', ), '', ([], ))])
1046
index2 = self.make_g_index('2', 1, [
1047
(('parent', ), ' 100 78', ([('tail', ), ('ghost', )], )),
1048
(('separate', ), '', ([], ))])
1049
combined_index = CombinedGraphIndex([index1, index2])
1051
self.combined_index = combined_index
1052
self.caught_entries = []
1053
add_callback = self.catch_add
1056
return _KnitGraphIndex(combined_index, lambda:True, deltas=deltas,
1057
add_callback=add_callback)
1059
def test_keys(self):
1060
index = self.two_graph_index()
1061
self.assertEqual(set([('tail',), ('tip',), ('parent',), ('separate',)]),
1064
def test_get_position(self):
1065
index = self.two_graph_index()
1066
self.assertEqual((index._graph_index._indices[0], 0, 100), index.get_position(('tip',)))
1067
self.assertEqual((index._graph_index._indices[1], 100, 78), index.get_position(('parent',)))
1069
def test_get_method_deltas(self):
1070
index = self.two_graph_index(deltas=True)
1071
self.assertEqual('fulltext', index.get_method(('tip',)))
1072
self.assertEqual('line-delta', index.get_method(('parent',)))
1074
def test_get_method_no_deltas(self):
1075
# check that the parent-history lookup is ignored with deltas=False.
1076
index = self.two_graph_index(deltas=False)
1077
self.assertEqual('fulltext', index.get_method(('tip',)))
1078
self.assertEqual('fulltext', index.get_method(('parent',)))
1080
def test_get_options_deltas(self):
1081
index = self.two_graph_index(deltas=True)
1082
self.assertEqual(['fulltext', 'no-eol'], index.get_options(('tip',)))
1083
self.assertEqual(['line-delta'], index.get_options(('parent',)))
1085
def test_get_options_no_deltas(self):
1086
# check that the parent-history lookup is ignored with deltas=False.
1087
index = self.two_graph_index(deltas=False)
1088
self.assertEqual(['fulltext', 'no-eol'], index.get_options(('tip',)))
1089
self.assertEqual(['fulltext'], index.get_options(('parent',)))
1091
def test_get_parent_map(self):
1092
index = self.two_graph_index()
1093
self.assertEqual({('parent',):(('tail',), ('ghost',))},
1094
index.get_parent_map([('parent',), ('ghost',)]))
1096
def catch_add(self, entries):
1097
self.caught_entries.append(entries)
1099
def test_add_no_callback_errors(self):
1100
index = self.two_graph_index()
1101
self.assertRaises(errors.ReadOnlyError, index.add_records,
1102
[(('new',), 'fulltext,no-eol', (None, 50, 60), ['separate'])])
1104
def test_add_version_smoke(self):
1105
index = self.two_graph_index(catch_adds=True)
1106
index.add_records([(('new',), 'fulltext,no-eol', (None, 50, 60),
1108
self.assertEqual([[(('new', ), 'N50 60', ((('separate',),),))]],
1109
self.caught_entries)
1111
def test_add_version_delta_not_delta_index(self):
1112
index = self.two_graph_index(catch_adds=True)
1113
self.assertRaises(errors.KnitCorrupt, index.add_records,
1114
[(('new',), 'no-eol,line-delta', (None, 0, 100), [('parent',)])])
1115
self.assertEqual([], self.caught_entries)
1117
def test_add_version_same_dup(self):
1118
index = self.two_graph_index(catch_adds=True)
1119
# options can be spelt two different ways
1120
index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 100), [('parent',)])])
1121
index.add_records([(('tip',), 'no-eol,fulltext', (None, 0, 100), [('parent',)])])
1122
# position/length are ignored (because each pack could have fulltext or
1123
# delta, and be at a different position.
1124
index.add_records([(('tip',), 'fulltext,no-eol', (None, 50, 100),
1126
index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 1000),
1128
# but neither should have added data:
1129
self.assertEqual([[], [], [], []], self.caught_entries)
1131
def test_add_version_different_dup(self):
1132
index = self.two_graph_index(deltas=True, catch_adds=True)
1134
self.assertRaises(errors.KnitCorrupt, index.add_records,
1135
[(('tip',), 'no-eol,line-delta', (None, 0, 100), [('parent',)])])
1136
self.assertRaises(errors.KnitCorrupt, index.add_records,
1137
[(('tip',), 'line-delta,no-eol', (None, 0, 100), [('parent',)])])
1138
self.assertRaises(errors.KnitCorrupt, index.add_records,
1139
[(('tip',), 'fulltext', (None, 0, 100), [('parent',)])])
1141
self.assertRaises(errors.KnitCorrupt, index.add_records,
1142
[(('tip',), 'fulltext,no-eol', (None, 0, 100), [])])
1143
self.assertEqual([], self.caught_entries)
1145
def test_add_versions_nodeltas(self):
1146
index = self.two_graph_index(catch_adds=True)
1148
(('new',), 'fulltext,no-eol', (None, 50, 60), [('separate',)]),
1149
(('new2',), 'fulltext', (None, 0, 6), [('new',)]),
1151
self.assertEqual([(('new', ), 'N50 60', ((('separate',),),)),
1152
(('new2', ), ' 0 6', ((('new',),),))],
1153
sorted(self.caught_entries[0]))
1154
self.assertEqual(1, len(self.caught_entries))
1156
def test_add_versions_deltas(self):
1157
index = self.two_graph_index(deltas=True, catch_adds=True)
1159
(('new',), 'fulltext,no-eol', (None, 50, 60), [('separate',)]),
1160
(('new2',), 'line-delta', (None, 0, 6), [('new',)]),
1162
self.assertEqual([(('new', ), 'N50 60', ((('separate',),), ())),
1163
(('new2', ), ' 0 6', ((('new',),), (('new',),), ))],
1164
sorted(self.caught_entries[0]))
1165
self.assertEqual(1, len(self.caught_entries))
1167
def test_add_versions_delta_not_delta_index(self):
1168
index = self.two_graph_index(catch_adds=True)
1169
self.assertRaises(errors.KnitCorrupt, index.add_records,
1170
[(('new',), 'no-eol,line-delta', (None, 0, 100), [('parent',)])])
1171
self.assertEqual([], self.caught_entries)
1173
def test_add_versions_random_id_accepted(self):
1174
index = self.two_graph_index(catch_adds=True)
1175
index.add_records([], random_id=True)
1177
def test_add_versions_same_dup(self):
1178
index = self.two_graph_index(catch_adds=True)
1179
# options can be spelt two different ways
1180
index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 100),
1182
index.add_records([(('tip',), 'no-eol,fulltext', (None, 0, 100),
1184
# position/length are ignored (because each pack could have fulltext or
1185
# delta, and be at a different position.
1186
index.add_records([(('tip',), 'fulltext,no-eol', (None, 50, 100),
1188
index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 1000),
1190
# but neither should have added data.
1191
self.assertEqual([[], [], [], []], self.caught_entries)
1193
def test_add_versions_different_dup(self):
1194
index = self.two_graph_index(deltas=True, catch_adds=True)
1196
self.assertRaises(errors.KnitCorrupt, index.add_records,
1197
[(('tip',), 'no-eol,line-delta', (None, 0, 100), [('parent',)])])
1198
self.assertRaises(errors.KnitCorrupt, index.add_records,
1199
[(('tip',), 'line-delta,no-eol', (None, 0, 100), [('parent',)])])
1200
self.assertRaises(errors.KnitCorrupt, index.add_records,
1201
[(('tip',), 'fulltext', (None, 0, 100), [('parent',)])])
1203
self.assertRaises(errors.KnitCorrupt, index.add_records,
1204
[(('tip',), 'fulltext,no-eol', (None, 0, 100), [])])
1205
# change options in the second record
1206
self.assertRaises(errors.KnitCorrupt, index.add_records,
1207
[(('tip',), 'fulltext,no-eol', (None, 0, 100), [('parent',)]),
1208
(('tip',), 'no-eol,line-delta', (None, 0, 100), [('parent',)])])
1209
self.assertEqual([], self.caught_entries)
1212
class TestNoParentsGraphIndexKnit(KnitTests):
1213
"""Tests for knits using _KnitGraphIndex with no parents."""
1215
def make_g_index(self, name, ref_lists=0, nodes=[]):
1216
builder = GraphIndexBuilder(ref_lists)
1217
for node, references in nodes:
1218
builder.add_node(node, references)
1219
stream = builder.finish()
1220
trans = self.get_transport()
1221
size = trans.put_file(name, stream)
1222
return GraphIndex(trans, name, size)
1224
def test_parents_deltas_incompatible(self):
1225
index = CombinedGraphIndex([])
1226
self.assertRaises(errors.KnitError, _KnitGraphIndex, lambda:True,
1227
index, deltas=True, parents=False)
1229
def two_graph_index(self, catch_adds=False):
1230
"""Build a two-graph index.
1232
:param deltas: If true, use underlying indices with two node-ref
1233
lists and 'parent' set to a delta-compressed against tail.
1235
# put several versions in the index.
1236
index1 = self.make_g_index('1', 0, [
1237
(('tip', ), 'N0 100'),
1239
index2 = self.make_g_index('2', 0, [
1240
(('parent', ), ' 100 78'),
1241
(('separate', ), '')])
1242
combined_index = CombinedGraphIndex([index1, index2])
1244
self.combined_index = combined_index
1245
self.caught_entries = []
1246
add_callback = self.catch_add
1249
return _KnitGraphIndex(combined_index, lambda:True, parents=False,
1250
add_callback=add_callback)
1252
def test_keys(self):
1253
index = self.two_graph_index()
1254
self.assertEqual(set([('tail',), ('tip',), ('parent',), ('separate',)]),
1257
def test_get_position(self):
1258
index = self.two_graph_index()
1259
self.assertEqual((index._graph_index._indices[0], 0, 100),
1260
index.get_position(('tip',)))
1261
self.assertEqual((index._graph_index._indices[1], 100, 78),
1262
index.get_position(('parent',)))
1264
def test_get_method(self):
1265
index = self.two_graph_index()
1266
self.assertEqual('fulltext', index.get_method(('tip',)))
1267
self.assertEqual(['fulltext'], index.get_options(('parent',)))
1269
def test_get_options(self):
1270
index = self.two_graph_index()
1271
self.assertEqual(['fulltext', 'no-eol'], index.get_options(('tip',)))
1272
self.assertEqual(['fulltext'], index.get_options(('parent',)))
1274
def test_get_parent_map(self):
1275
index = self.two_graph_index()
1276
self.assertEqual({('parent',):None},
1277
index.get_parent_map([('parent',), ('ghost',)]))
1279
def catch_add(self, entries):
1280
self.caught_entries.append(entries)
1282
def test_add_no_callback_errors(self):
1283
index = self.two_graph_index()
1284
self.assertRaises(errors.ReadOnlyError, index.add_records,
1285
[(('new',), 'fulltext,no-eol', (None, 50, 60), [('separate',)])])
1287
def test_add_version_smoke(self):
1288
index = self.two_graph_index(catch_adds=True)
1289
index.add_records([(('new',), 'fulltext,no-eol', (None, 50, 60), [])])
1290
self.assertEqual([[(('new', ), 'N50 60')]],
1291
self.caught_entries)
1293
def test_add_version_delta_not_delta_index(self):
1294
index = self.two_graph_index(catch_adds=True)
1295
self.assertRaises(errors.KnitCorrupt, index.add_records,
1296
[(('new',), 'no-eol,line-delta', (None, 0, 100), [])])
1297
self.assertEqual([], self.caught_entries)
1299
def test_add_version_same_dup(self):
1300
index = self.two_graph_index(catch_adds=True)
1301
# options can be spelt two different ways
1302
index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 100), [])])
1303
index.add_records([(('tip',), 'no-eol,fulltext', (None, 0, 100), [])])
1304
# position/length are ignored (because each pack could have fulltext or
1305
# delta, and be at a different position.
1306
index.add_records([(('tip',), 'fulltext,no-eol', (None, 50, 100), [])])
1307
index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 1000), [])])
1308
# but neither should have added data.
1309
self.assertEqual([[], [], [], []], self.caught_entries)
1311
def test_add_version_different_dup(self):
1312
index = self.two_graph_index(catch_adds=True)
1314
self.assertRaises(errors.KnitCorrupt, index.add_records,
1315
[(('tip',), 'no-eol,line-delta', (None, 0, 100), [])])
1316
self.assertRaises(errors.KnitCorrupt, index.add_records,
1317
[(('tip',), 'line-delta,no-eol', (None, 0, 100), [])])
1318
self.assertRaises(errors.KnitCorrupt, index.add_records,
1319
[(('tip',), 'fulltext', (None, 0, 100), [])])
1321
self.assertRaises(errors.KnitCorrupt, index.add_records,
1322
[(('tip',), 'fulltext,no-eol', (None, 0, 100), [('parent',)])])
1323
self.assertEqual([], self.caught_entries)
1325
def test_add_versions(self):
1326
index = self.two_graph_index(catch_adds=True)
1328
(('new',), 'fulltext,no-eol', (None, 50, 60), []),
1329
(('new2',), 'fulltext', (None, 0, 6), []),
1331
self.assertEqual([(('new', ), 'N50 60'), (('new2', ), ' 0 6')],
1332
sorted(self.caught_entries[0]))
1333
self.assertEqual(1, len(self.caught_entries))
1335
def test_add_versions_delta_not_delta_index(self):
1336
index = self.two_graph_index(catch_adds=True)
1337
self.assertRaises(errors.KnitCorrupt, index.add_records,
1338
[(('new',), 'no-eol,line-delta', (None, 0, 100), [('parent',)])])
1339
self.assertEqual([], self.caught_entries)
1341
def test_add_versions_parents_not_parents_index(self):
1342
index = self.two_graph_index(catch_adds=True)
1343
self.assertRaises(errors.KnitCorrupt, index.add_records,
1344
[(('new',), 'no-eol,fulltext', (None, 0, 100), [('parent',)])])
1345
self.assertEqual([], self.caught_entries)
1347
def test_add_versions_random_id_accepted(self):
1348
index = self.two_graph_index(catch_adds=True)
1349
index.add_records([], random_id=True)
1351
def test_add_versions_same_dup(self):
1352
index = self.two_graph_index(catch_adds=True)
1353
# options can be spelt two different ways
1354
index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 100), [])])
1355
index.add_records([(('tip',), 'no-eol,fulltext', (None, 0, 100), [])])
1356
# position/length are ignored (because each pack could have fulltext or
1357
# delta, and be at a different position.
1358
index.add_records([(('tip',), 'fulltext,no-eol', (None, 50, 100), [])])
1359
index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 1000), [])])
1360
# but neither should have added data.
1361
self.assertEqual([[], [], [], []], self.caught_entries)
1363
def test_add_versions_different_dup(self):
1364
index = self.two_graph_index(catch_adds=True)
1366
self.assertRaises(errors.KnitCorrupt, index.add_records,
1367
[(('tip',), 'no-eol,line-delta', (None, 0, 100), [])])
1368
self.assertRaises(errors.KnitCorrupt, index.add_records,
1369
[(('tip',), 'line-delta,no-eol', (None, 0, 100), [])])
1370
self.assertRaises(errors.KnitCorrupt, index.add_records,
1371
[(('tip',), 'fulltext', (None, 0, 100), [])])
1373
self.assertRaises(errors.KnitCorrupt, index.add_records,
1374
[(('tip',), 'fulltext,no-eol', (None, 0, 100), [('parent',)])])
1375
# change options in the second record
1376
self.assertRaises(errors.KnitCorrupt, index.add_records,
1377
[(('tip',), 'fulltext,no-eol', (None, 0, 100), []),
1378
(('tip',), 'no-eol,line-delta', (None, 0, 100), [])])
1379
self.assertEqual([], self.caught_entries)
1382
class TestStacking(KnitTests):
1384
def get_basis_and_test_knit(self):
1385
basis = self.make_test_knit(name='basis')
1386
basis = RecordingVersionedFilesDecorator(basis)
1387
test = self.make_test_knit(name='test')
1388
test.add_fallback_versioned_files(basis)
1391
def test_add_fallback_versioned_files(self):
1392
basis = self.make_test_knit(name='basis')
1393
test = self.make_test_knit(name='test')
1394
# It must not error; other tests test that the fallback is referred to
1395
# when accessing data.
1396
test.add_fallback_versioned_files(basis)
1398
def test_add_lines(self):
1399
# lines added to the test are not added to the basis
1400
basis, test = self.get_basis_and_test_knit()
1402
key_basis = ('bar',)
1403
key_cross_border = ('quux',)
1404
key_delta = ('zaphod',)
1405
test.add_lines(key, (), ['foo\n'])
1406
self.assertEqual({}, basis.get_parent_map([key]))
1407
# lines added to the test that reference across the stack do a
1409
basis.add_lines(key_basis, (), ['foo\n'])
1411
test.add_lines(key_cross_border, (key_basis,), ['foo\n'])
1412
self.assertEqual('fulltext', test._index.get_method(key_cross_border))
1413
self.assertEqual([("get_parent_map", set([key_basis]))], basis.calls)
1414
# Subsequent adds do delta.
1416
test.add_lines(key_delta, (key_cross_border,), ['foo\n'])
1417
self.assertEqual('line-delta', test._index.get_method(key_delta))
1418
self.assertEqual([], basis.calls)
1420
def test_annotate(self):
1421
# annotations from the test knit are answered without asking the basis
1422
basis, test = self.get_basis_and_test_knit()
1424
key_basis = ('bar',)
1425
key_missing = ('missing',)
1426
test.add_lines(key, (), ['foo\n'])
1427
details = test.annotate(key)
1428
self.assertEqual([(key, 'foo\n')], details)
1429
self.assertEqual([], basis.calls)
1430
# But texts that are not in the test knit are looked for in the basis
1432
basis.add_lines(key_basis, (), ['foo\n', 'bar\n'])
1434
details = test.annotate(key_basis)
1435
self.assertEqual([(key_basis, 'foo\n'), (key_basis, 'bar\n')], details)
1436
# Not optimised to date:
1437
# self.assertEqual([("annotate", key_basis)], basis.calls)
1438
self.assertEqual([('get_parent_map', set([key_basis])),
1439
('get_parent_map', set([key_basis])),
1440
('get_parent_map', set([key_basis])),
1441
('get_record_stream', [key_basis], 'unordered', True)],
1444
def test_check(self):
1445
# At the moment checking a stacked knit does implicitly check the
1447
basis, test = self.get_basis_and_test_knit()
1450
def test_get_parent_map(self):
1451
# parents in the test knit are answered without asking the basis
1452
basis, test = self.get_basis_and_test_knit()
1454
key_basis = ('bar',)
1455
key_missing = ('missing',)
1456
test.add_lines(key, (), [])
1457
parent_map = test.get_parent_map([key])
1458
self.assertEqual({key: ()}, parent_map)
1459
self.assertEqual([], basis.calls)
1460
# But parents that are not in the test knit are looked for in the basis
1461
basis.add_lines(key_basis, (), [])
1463
parent_map = test.get_parent_map([key, key_basis, key_missing])
1464
self.assertEqual({key: (),
1465
key_basis: ()}, parent_map)
1466
self.assertEqual([("get_parent_map", set([key_basis, key_missing]))],
1469
def test_get_record_stream_unordered_fulltexts(self):
1470
# records from the test knit are answered without asking the basis:
1471
basis, test = self.get_basis_and_test_knit()
1473
key_basis = ('bar',)
1474
key_missing = ('missing',)
1475
test.add_lines(key, (), ['foo\n'])
1476
records = list(test.get_record_stream([key], 'unordered', True))
1477
self.assertEqual(1, len(records))
1478
self.assertEqual([], basis.calls)
1479
# Missing (from test knit) objects are retrieved from the basis:
1480
basis.add_lines(key_basis, (), ['foo\n', 'bar\n'])
1482
records = list(test.get_record_stream([key_basis, key_missing],
1484
self.assertEqual(2, len(records))
1485
calls = list(basis.calls)
1486
for record in records:
1487
self.assertSubset([record.key], (key_basis, key_missing))
1488
if record.key == key_missing:
1489
self.assertIsInstance(record, AbsentContentFactory)
1491
reference = list(basis.get_record_stream([key_basis],
1492
'unordered', True))[0]
1493
self.assertEqual(reference.key, record.key)
1494
self.assertEqual(reference.sha1, record.sha1)
1495
self.assertEqual(reference.storage_kind, record.storage_kind)
1496
self.assertEqual(reference.get_bytes_as(reference.storage_kind),
1497
record.get_bytes_as(record.storage_kind))
1498
self.assertEqual(reference.get_bytes_as('fulltext'),
1499
record.get_bytes_as('fulltext'))
1500
# It's not strictly minimal, but it seems reasonable for now for it to
1501
# ask which fallbacks have which parents.
1503
("get_parent_map", set([key_basis, key_missing])),
1504
("get_record_stream", [key_basis], 'unordered', True)],
1507
def test_get_record_stream_ordered_fulltexts(self):
1508
# ordering is preserved down into the fallback store.
1509
basis, test = self.get_basis_and_test_knit()
1511
key_basis = ('bar',)
1512
key_basis_2 = ('quux',)
1513
key_missing = ('missing',)
1514
test.add_lines(key, (key_basis,), ['foo\n'])
1515
# Missing (from test knit) objects are retrieved from the basis:
1516
basis.add_lines(key_basis, (key_basis_2,), ['foo\n', 'bar\n'])
1517
basis.add_lines(key_basis_2, (), ['quux\n'])
1519
# ask for in non-topological order
1520
records = list(test.get_record_stream(
1521
[key, key_basis, key_missing, key_basis_2], 'topological', True))
1522
self.assertEqual(4, len(records))
1524
for record in records:
1525
self.assertSubset([record.key],
1526
(key_basis, key_missing, key_basis_2, key))
1527
if record.key == key_missing:
1528
self.assertIsInstance(record, AbsentContentFactory)
1530
results.append((record.key, record.sha1, record.storage_kind,
1531
record.get_bytes_as('fulltext')))
1532
calls = list(basis.calls)
1533
order = [record[0] for record in results]
1534
self.assertEqual([key_basis_2, key_basis, key], order)
1535
for result in results:
1536
if result[0] == key:
1540
record = source.get_record_stream([result[0]], 'unordered',
1542
self.assertEqual(record.key, result[0])
1543
self.assertEqual(record.sha1, result[1])
1544
self.assertEqual(record.storage_kind, result[2])
1545
self.assertEqual(record.get_bytes_as('fulltext'), result[3])
1546
# It's not strictly minimal, but it seems reasonable for now for it to
1547
# ask which fallbacks have which parents.
1549
("get_parent_map", set([key_basis, key_basis_2, key_missing])),
1550
# unordered is asked for by the underlying worker as it still
1551
# buffers everything while answering - which is a problem!
1552
("get_record_stream", [key_basis_2, key_basis], 'unordered', True)],
1555
def test_get_record_stream_unordered_deltas(self):
1556
# records from the test knit are answered without asking the basis:
1557
basis, test = self.get_basis_and_test_knit()
1559
key_basis = ('bar',)
1560
key_missing = ('missing',)
1561
test.add_lines(key, (), ['foo\n'])
1562
records = list(test.get_record_stream([key], 'unordered', False))
1563
self.assertEqual(1, len(records))
1564
self.assertEqual([], basis.calls)
1565
# Missing (from test knit) objects are retrieved from the basis:
1566
basis.add_lines(key_basis, (), ['foo\n', 'bar\n'])
1568
records = list(test.get_record_stream([key_basis, key_missing],
1569
'unordered', False))
1570
self.assertEqual(2, len(records))
1571
calls = list(basis.calls)
1572
for record in records:
1573
self.assertSubset([record.key], (key_basis, key_missing))
1574
if record.key == key_missing:
1575
self.assertIsInstance(record, AbsentContentFactory)
1577
reference = list(basis.get_record_stream([key_basis],
1578
'unordered', False))[0]
1579
self.assertEqual(reference.key, record.key)
1580
self.assertEqual(reference.sha1, record.sha1)
1581
self.assertEqual(reference.storage_kind, record.storage_kind)
1582
self.assertEqual(reference.get_bytes_as(reference.storage_kind),
1583
record.get_bytes_as(record.storage_kind))
1584
# It's not strictly minimal, but it seems reasonable for now for it to
1585
# ask which fallbacks have which parents.
1587
("get_parent_map", set([key_basis, key_missing])),
1588
("get_record_stream", [key_basis], 'unordered', False)],
1591
def test_get_record_stream_ordered_deltas(self):
1592
# ordering is preserved down into the fallback store.
1593
basis, test = self.get_basis_and_test_knit()
1595
key_basis = ('bar',)
1596
key_basis_2 = ('quux',)
1597
key_missing = ('missing',)
1598
test.add_lines(key, (key_basis,), ['foo\n'])
1599
# Missing (from test knit) objects are retrieved from the basis:
1600
basis.add_lines(key_basis, (key_basis_2,), ['foo\n', 'bar\n'])
1601
basis.add_lines(key_basis_2, (), ['quux\n'])
1603
# ask for in non-topological order
1604
records = list(test.get_record_stream(
1605
[key, key_basis, key_missing, key_basis_2], 'topological', False))
1606
self.assertEqual(4, len(records))
1608
for record in records:
1609
self.assertSubset([record.key],
1610
(key_basis, key_missing, key_basis_2, key))
1611
if record.key == key_missing:
1612
self.assertIsInstance(record, AbsentContentFactory)
1614
results.append((record.key, record.sha1, record.storage_kind,
1615
record.get_bytes_as(record.storage_kind)))
1616
calls = list(basis.calls)
1617
order = [record[0] for record in results]
1618
self.assertEqual([key_basis_2, key_basis, key], order)
1619
for result in results:
1620
if result[0] == key:
1624
record = source.get_record_stream([result[0]], 'unordered',
1626
self.assertEqual(record.key, result[0])
1627
self.assertEqual(record.sha1, result[1])
1628
self.assertEqual(record.storage_kind, result[2])
1629
self.assertEqual(record.get_bytes_as(record.storage_kind), result[3])
1630
# It's not strictly minimal, but it seems reasonable for now for it to
1631
# ask which fallbacks have which parents.
1633
("get_parent_map", set([key_basis, key_basis_2, key_missing])),
1634
("get_record_stream", [key_basis_2, key_basis], 'topological', False)],
1637
def test_get_sha1s(self):
1638
# sha1's in the test knit are answered without asking the basis
1639
basis, test = self.get_basis_and_test_knit()
1641
key_basis = ('bar',)
1642
key_missing = ('missing',)
1643
test.add_lines(key, (), ['foo\n'])
1644
key_sha1sum = sha.new('foo\n').hexdigest()
1645
sha1s = test.get_sha1s([key])
1646
self.assertEqual({key: key_sha1sum}, sha1s)
1647
self.assertEqual([], basis.calls)
1648
# But texts that are not in the test knit are looked for in the basis
1649
# directly (rather than via text reconstruction) so that remote servers
1650
# etc don't have to answer with full content.
1651
basis.add_lines(key_basis, (), ['foo\n', 'bar\n'])
1652
basis_sha1sum = sha.new('foo\nbar\n').hexdigest()
1654
sha1s = test.get_sha1s([key, key_missing, key_basis])
1655
self.assertEqual({key: key_sha1sum,
1656
key_basis: basis_sha1sum}, sha1s)
1657
self.assertEqual([("get_sha1s", set([key_basis, key_missing]))],
1660
def test_insert_record_stream(self):
1661
# records are inserted as normal; insert_record_stream builds on
1662
# add_lines, so a smoke test should be all that's needed:
1664
key_basis = ('bar',)
1665
key_delta = ('zaphod',)
1666
basis, test = self.get_basis_and_test_knit()
1667
source = self.make_test_knit(name='source')
1668
basis.add_lines(key_basis, (), ['foo\n'])
1670
source.add_lines(key_basis, (), ['foo\n'])
1671
source.add_lines(key_delta, (key_basis,), ['bar\n'])
1672
stream = source.get_record_stream([key_delta], 'unordered', False)
1673
test.insert_record_stream(stream)
1674
self.assertEqual([("get_parent_map", set([key_basis]))],
1676
self.assertEqual({key_delta:(key_basis,)},
1677
test.get_parent_map([key_delta]))
1678
self.assertEqual('bar\n', test.get_record_stream([key_delta],
1679
'unordered', True).next().get_bytes_as('fulltext'))
1681
def test_iter_lines_added_or_present_in_keys(self):
1682
# Lines from the basis are returned, and lines for a given key are only
1686
# all sources are asked for keys:
1687
basis, test = self.get_basis_and_test_knit()
1688
basis.add_lines(key1, (), ["foo"])
1690
lines = list(test.iter_lines_added_or_present_in_keys([key1]))
1691
self.assertEqual([("foo\n", key1)], lines)
1692
self.assertEqual([("iter_lines_added_or_present_in_keys", set([key1]))],
1694
# keys in both are not duplicated:
1695
test.add_lines(key2, (), ["bar\n"])
1696
basis.add_lines(key2, (), ["bar\n"])
1698
lines = list(test.iter_lines_added_or_present_in_keys([key2]))
1699
self.assertEqual([("bar\n", key2)], lines)
1700
self.assertEqual([], basis.calls)
1702
def test_keys(self):
1705
# all sources are asked for keys:
1706
basis, test = self.get_basis_and_test_knit()
1708
self.assertEqual(set(), set(keys))
1709
self.assertEqual([("keys",)], basis.calls)
1710
# keys from a basis are returned:
1711
basis.add_lines(key1, (), [])
1714
self.assertEqual(set([key1]), set(keys))
1715
self.assertEqual([("keys",)], basis.calls)
1716
# keys in both are not duplicated:
1717
test.add_lines(key2, (), [])
1718
basis.add_lines(key2, (), [])
1721
self.assertEqual(2, len(keys))
1722
self.assertEqual(set([key1, key2]), set(keys))
1723
self.assertEqual([("keys",)], basis.calls)
1725
def test_add_mpdiffs(self):
1726
# records are inserted as normal; add_mpdiff builds on
1727
# add_lines, so a smoke test should be all that's needed:
1729
key_basis = ('bar',)
1730
key_delta = ('zaphod',)
1731
basis, test = self.get_basis_and_test_knit()
1732
source = self.make_test_knit(name='source')
1733
basis.add_lines(key_basis, (), ['foo\n'])
1735
source.add_lines(key_basis, (), ['foo\n'])
1736
source.add_lines(key_delta, (key_basis,), ['bar\n'])
1737
diffs = source.make_mpdiffs([key_delta])
1738
test.add_mpdiffs([(key_delta, (key_basis,),
1739
source.get_sha1s([key_delta])[key_delta], diffs[0])])
1740
self.assertEqual([("get_parent_map", set([key_basis])),
1741
('get_record_stream', [key_basis], 'unordered', True),
1742
('get_parent_map', set([key_basis]))],
1744
self.assertEqual({key_delta:(key_basis,)},
1745
test.get_parent_map([key_delta]))
1746
self.assertEqual('bar\n', test.get_record_stream([key_delta],
1747
'unordered', True).next().get_bytes_as('fulltext'))
1749
def test_make_mpdiffs(self):
1750
# Generating an mpdiff across a stacking boundary should detect parent
1754
key_right = ('zaphod',)
1755
basis, test = self.get_basis_and_test_knit()
1756
basis.add_lines(key_left, (), ['bar\n'])
1757
basis.add_lines(key_right, (), ['zaphod\n'])
1759
test.add_lines(key, (key_left, key_right),
1760
['bar\n', 'foo\n', 'zaphod\n'])
1761
diffs = test.make_mpdiffs([key])
1763
multiparent.MultiParent([multiparent.ParentText(0, 0, 0, 1),
1764
multiparent.NewText(['foo\n']),
1765
multiparent.ParentText(1, 0, 2, 1)])],
1767
self.assertEqual(4, len(basis.calls))
1769
("get_parent_map", set([key_left, key_right])),
1770
("get_parent_map", set([key_left, key_right])),
1771
("get_parent_map", set([key_left, key_right])),
1774
last_call = basis.calls[3]
1775
self.assertEqual('get_record_stream', last_call[0])
1776
self.assertEqual(set([key_left, key_right]), set(last_call[1]))
1777
self.assertEqual('unordered', last_call[2])
1778
self.assertEqual(True, last_call[3])
1224
self.assertRaises(KnitHeaderError, self.make_test_knit)