359
359
"""Ensure that iteration through ids works properly"""
360
360
btree = self.make_tree_1()[0]
361
361
self.assertEqual(self.sorted_ids(btree),
362
[inventory.ROOT_ID, b'a', b'b', b'c', b'd'])
362
[inventory.ROOT_ID, b'a', b'b', b'c', b'd'])
363
363
btree.note_deletion("grandparent/parent/file")
364
364
btree.note_id(b"e", "grandparent/alt_parent/fool", kind="directory")
365
365
btree.note_last_changed("grandparent/alt_parent/fool",
366
366
"revisionidiguess")
367
367
self.assertEqual(self.sorted_ids(btree),
368
[inventory.ROOT_ID, b'a', b'b', b'd', b'e'])
368
[inventory.ROOT_ID, b'a', b'b', b'd', b'e'])
371
371
class BundleTester1(tests.TestCaseWithTransport):
596
596
for path, status, kind, fileid, entry in base_files:
597
597
# Check that the meta information is the same
598
self.assertEqual(base_tree.get_file_size(path),
599
to_tree.get_file_size(to_tree.id2path(fileid)))
600
self.assertEqual(base_tree.get_file_sha1(path),
601
to_tree.get_file_sha1(to_tree.id2path(fileid)))
599
base_tree.get_file_size(path),
600
to_tree.get_file_size(to_tree.id2path(fileid)))
602
base_tree.get_file_sha1(path),
603
to_tree.get_file_sha1(to_tree.id2path(fileid)))
602
604
# Check that the contents are the same
603
605
# This is pretty expensive
604
606
# self.assertEqual(base_tree.get_file(fileid).read(),
618
620
# Make sure we can handle files with spaces, tabs, other
619
621
# bogus characters
620
622
self.build_tree([
623
, 'b1/dir/filein subdir.c'
624
, 'b1/dir/WithCaps.txt'
625
, 'b1/dir/ pre space'
628
, 'b1/sub/sub/nonempty.txt'
623
'b1/with space.txt', 'b1/dir/', 'b1/dir/filein subdir.c', 'b1/dir/WithCaps.txt', 'b1/dir/ pre space', 'b1/sub/', 'b1/sub/sub/', 'b1/sub/sub/nonempty.txt'
630
625
self.build_tree_contents([('b1/sub/sub/emptyfile.txt', b''),
631
626
('b1/dir/nolastnewline.txt', b'bloop')])
632
627
tt = TreeTransform(self.tree1)
636
631
# a (length-prefixed) record containing it later.
637
632
self.tree1.add('with space.txt', b'withspace-id')
640
, 'dir/filein subdir.c'
643
, 'dir/nolastnewline.txt'
646
, 'sub/sub/nonempty.txt'
647
, 'sub/sub/emptyfile.txt'
634
'dir', 'dir/filein subdir.c', 'dir/WithCaps.txt', 'dir/ pre space', 'dir/nolastnewline.txt', 'sub', 'sub/sub', 'sub/sub/nonempty.txt', 'sub/sub/emptyfile.txt'
649
636
self.tree1.commit('add whitespace', rev_id=b'a@cset-0-2')
651
638
bundle = self.get_valid_bundle(b'a@cset-0-1', b'a@cset-0-2')
668
653
bundle = self.get_valid_bundle(b'a@cset-0-2', b'a@cset-0-3')
669
654
self.assertRaises((errors.TestamentMismatch,
670
errors.VersionedFileInvalidChecksum,
671
errors.BadBundle), self.get_invalid_bundle,
672
b'a@cset-0-2', b'a@cset-0-3')
655
errors.VersionedFileInvalidChecksum,
656
errors.BadBundle), self.get_invalid_bundle,
657
b'a@cset-0-2', b'a@cset-0-3')
673
658
# Check a rollup bundle
674
659
bundle = self.get_valid_bundle(b'null:', b'a@cset-0-3')
682
667
bundle = self.get_valid_bundle(b'null:', b'a@cset-0-4')
685
with open('b1/sub/dir/WithCaps.txt', 'ab') as f: f.write(b'\nAdding some text\n')
686
with open('b1/sub/dir/ pre space', 'ab') as f: f.write(
687
b'\r\nAdding some\r\nDOS format lines\r\n')
688
with open('b1/sub/dir/nolastnewline.txt', 'ab') as f: f.write(b'\n')
670
with open('b1/sub/dir/WithCaps.txt', 'ab') as f:
671
f.write(b'\nAdding some text\n')
672
with open('b1/sub/dir/ pre space', 'ab') as f:
674
b'\r\nAdding some\r\nDOS format lines\r\n')
675
with open('b1/sub/dir/nolastnewline.txt', 'ab') as f:
689
677
self.tree1.rename_one('sub/dir/ pre space',
690
678
'sub/ start space')
691
679
self.tree1.commit('Modified files', rev_id=b'a@cset-0-5')
725
713
if getattr(bundle, 'revision_tree', None) is not None:
726
714
# Not all bundle formats supports revision_tree
727
715
bund_tree = bundle.revision_tree(self.b1.repository, b'l@cset-0-1')
728
self.assertEqual(link_target, bund_tree.get_symlink_target(link_name))
717
link_target, bund_tree.get_symlink_target(link_name))
730
719
tt = TreeTransform(self.tree1)
731
720
trans_id = tt.trans_id_tree_path(link_name)
771
760
tt = TreeTransform(self.tree1)
774
tt.new_file('file', tt.root, [b'\x00\n\x00\r\x01\n\x02\r\xff'], b'binary-1')
763
tt.new_file('file', tt.root, [
764
b'\x00\n\x00\r\x01\n\x02\r\xff'], b'binary-1')
775
765
tt.new_file('file2', tt.root, [b'\x01\n\x02\r\x03\n\x04\r\xff'],
778
768
self.tree1.commit('add binary', rev_id=b'b@cset-0-1')
779
769
self.get_valid_bundle(b'null:', b'b@cset-0-1')
840
830
self.tree1 = self.make_branch_and_tree('b1')
841
831
self.b1 = self.tree1.branch
843
with open('b1/one', 'wb') as f: f.write(b'one\n')
833
with open('b1/one', 'wb') as f:
844
835
self.tree1.add('one')
845
836
self.tree1.commit('add file', rev_id=b'a@cset-0-1')
846
with open('b1/one', 'wb') as f: f.write(b'two\n')
837
with open('b1/one', 'wb') as f:
847
839
self.tree1.commit('modify', rev_id=b'a@cset-0-2')
848
with open('b1/one', 'wb') as f: f.write(b'three\n')
840
with open('b1/one', 'wb') as f:
849
842
self.tree1.commit('modify', rev_id=b'a@cset-0-3')
850
843
bundle_file = BytesIO()
851
844
rev_ids = write_bundle(self.tree1.branch.repository, b'a@cset-0-3',
930
922
bundle = self.get_valid_bundle(b'null:', b'white-1')
933
with open('b1/trailing space ', 'ab') as f: f.write(b'add some text\n')
925
with open('b1/trailing space ', 'ab') as f:
926
f.write(b'add some text\n')
934
927
self.tree1.commit('add text', rev_id=b'white-2')
936
929
bundle = self.get_valid_bundle(b'white-1', b'white-2')
998
991
tree.lock_write()
999
992
self.addCleanup(tree.unlock)
1000
993
tree.add([''], [b'TREE_ROOT'])
1001
tree.commit('One', revprops={u'one': 'two', u'empty': ''}, rev_id=b'rev1')
994
tree.commit('One', revprops={u'one': 'two',
995
u'empty': ''}, rev_id=b'rev1')
1002
996
self.b1 = tree.branch
1003
997
bundle_sio, revision_ids = self.create_bundle_text(b'null:', b'rev1')
1004
998
bundle = read_bundle(bundle_sio)
1005
999
revision_info = bundle.revisions[0]
1006
1000
self.assertEqual(b'rev1', revision_info.revision_id)
1007
1001
rev = revision_info.as_revision()
1008
self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
1002
self.assertEqual({'branch-nick': 'tree', 'empty': '', 'one': 'two'},
1009
1003
rev.properties)
1011
1005
def test_bundle_sorted_properties(self):
1017
1011
tree.add([''], [b'TREE_ROOT'])
1018
1012
tree.commit('One', rev_id=b'rev1',
1019
revprops={u'a':'4', u'b':'3', u'c':'2', u'd':'1'})
1013
revprops={u'a': '4', u'b': '3', u'c': '2', u'd': '1'})
1020
1014
self.b1 = tree.branch
1021
1015
bundle_sio, revision_ids = self.create_bundle_text(b'null:', b'rev1')
1022
1016
bundle = read_bundle(bundle_sio)
1023
1017
revision_info = bundle.revisions[0]
1024
1018
self.assertEqual(b'rev1', revision_info.revision_id)
1025
1019
rev = revision_info.as_revision()
1026
self.assertEqual({'branch-nick':'tree', 'a':'4', 'b':'3', 'c':'2',
1027
'd':'1'}, rev.properties)
1020
self.assertEqual({'branch-nick': 'tree', 'a': '4', 'b': '3', 'c': '2',
1021
'd': '1'}, rev.properties)
1029
1023
def test_bundle_unicode_properties(self):
1030
1024
"""We should be able to round trip a non-ascii property."""
1039
1033
# However, Testaments assert than they are str(), and thus should not
1041
1035
tree.commit('One', rev_id=b'rev1',
1042
revprops={u'omega':u'\u03a9', u'alpha':u'\u03b1'})
1036
revprops={u'omega': u'\u03a9', u'alpha': u'\u03b1'})
1043
1037
self.b1 = tree.branch
1044
1038
bundle_sio, revision_ids = self.create_bundle_text(b'null:', b'rev1')
1045
1039
bundle = read_bundle(bundle_sio)
1046
1040
revision_info = bundle.revisions[0]
1047
1041
self.assertEqual(b'rev1', revision_info.revision_id)
1048
1042
rev = revision_info.as_revision()
1049
self.assertEqual({'branch-nick':'tree', 'omega':u'\u03a9',
1050
'alpha':u'\u03b1'}, rev.properties)
1043
self.assertEqual({'branch-nick': 'tree', 'omega': u'\u03a9',
1044
'alpha': u'\u03b1'}, rev.properties)
1052
1046
def test_bundle_with_ghosts(self):
1053
1047
tree = self.make_branch_and_tree('tree')
1094
1088
root_id = inv.root.file_id
1095
1089
repo.lock_read()
1096
1090
self.addCleanup(repo.unlock)
1097
self.assertEqual({(root_id, b'rev1'):(),
1098
(root_id, b'rev2'):((root_id, b'rev1'),)},
1099
repo.texts.get_parent_map([(root_id, b'rev1'), (root_id, b'rev2')]))
1091
self.assertEqual({(root_id, b'rev1'): (),
1092
(root_id, b'rev2'): ((root_id, b'rev1'),)},
1093
repo.texts.get_parent_map([(root_id, b'rev1'), (root_id, b'rev2')]))
1101
1095
def test_inv_hash_across_serializers(self):
1102
1096
repo = self.make_repo_with_installed_revisions()
1180
1174
file_ids = set(
1181
1175
(f, r) for b, m, k, r, f in bundle.get_bundle_reader().iter_records()
1182
1176
if f is not None)
1183
self.assertEqual({(b'file2-id', b'rev3'), (b'file3-id', rev2)}, file_ids)
1178
{(b'file2-id', b'rev3'), (b'file3-id', rev2)}, file_ids)
1184
1179
bundle.install_revisions(target.branch.repository)
1194
1189
tree.lock_write()
1195
1190
self.addCleanup(tree.unlock)
1196
1191
tree.add([''], [b'TREE_ROOT'])
1197
tree.commit('One', revprops={u'one':'two', u'empty':''}, rev_id=b'rev1')
1192
tree.commit('One', revprops={u'one': 'two',
1193
u'empty': ''}, rev_id=b'rev1')
1198
1194
self.b1 = tree.branch
1199
1195
bundle_sio, revision_ids = self.create_bundle_text(b'null:', b'rev1')
1200
1196
self.assertContainsRe(bundle_sio.getvalue(),
1202
1198
b'# branch-nick: tree\n'
1204
1200
b'# one: two\n'
1206
1202
bundle = read_bundle(bundle_sio)
1207
1203
revision_info = bundle.revisions[0]
1208
1204
self.assertEqual(b'rev1', revision_info.revision_id)
1209
1205
rev = revision_info.as_revision()
1210
self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
1206
self.assertEqual({'branch-nick': 'tree', 'empty': '', 'one': 'two'},
1211
1207
rev.properties)
1213
1209
def get_bundle_tree(self, bundle, revision_id):
1226
1222
tree.lock_write()
1227
1223
self.addCleanup(tree.unlock)
1228
1224
tree.add([''], [b'TREE_ROOT'])
1229
tree.commit('One', revprops={u'one':'two', u'empty':''}, rev_id=b'rev1')
1225
tree.commit('One', revprops={u'one': 'two',
1226
u'empty': ''}, rev_id=b'rev1')
1230
1227
self.b1 = tree.branch
1231
1228
bundle_sio, revision_ids = self.create_bundle_text(b'null:', b'rev1')
1232
1229
txt = bundle_sio.getvalue()
1233
1230
loc = txt.find(b'# empty: ') + len(b'# empty:')
1234
1231
# Create a new bundle, which strips the trailing space after empty
1235
bundle_sio = BytesIO(txt[:loc] + txt[loc+1:])
1232
bundle_sio = BytesIO(txt[:loc] + txt[loc + 1:])
1237
1234
self.assertContainsRe(bundle_sio.getvalue(),
1238
1235
b'# properties:\n'
1239
1236
b'# branch-nick: tree\n'
1241
1238
b'# one: two\n'
1243
1240
bundle = read_bundle(bundle_sio)
1244
1241
revision_info = bundle.revisions[0]
1245
1242
self.assertEqual(b'rev1', revision_info.revision_id)
1246
1243
rev = revision_info.as_revision()
1247
self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
1244
self.assertEqual({'branch-nick': 'tree', 'empty': '', 'one': 'two'},
1248
1245
rev.properties)
1250
1247
def test_bundle_sorted_properties(self):
1256
1253
tree.add([''], [b'TREE_ROOT'])
1257
1254
tree.commit('One', rev_id=b'rev1',
1258
revprops={u'a':'4', u'b':'3', u'c':'2', u'd':'1'})
1255
revprops={u'a': '4', u'b': '3', u'c': '2', u'd': '1'})
1259
1256
self.b1 = tree.branch
1260
1257
bundle_sio, revision_ids = self.create_bundle_text(b'null:', b'rev1')
1261
1258
self.assertContainsRe(bundle_sio.getvalue(),
1265
1262
b'# branch-nick: tree\n'
1269
1266
bundle = read_bundle(bundle_sio)
1270
1267
revision_info = bundle.revisions[0]
1271
1268
self.assertEqual(b'rev1', revision_info.revision_id)
1272
1269
rev = revision_info.as_revision()
1273
self.assertEqual({'branch-nick':'tree', 'a':'4', 'b':'3', 'c':'2',
1274
'd':'1'}, rev.properties)
1270
self.assertEqual({'branch-nick': 'tree', 'a': '4', 'b': '3', 'c': '2',
1271
'd': '1'}, rev.properties)
1276
1273
def test_bundle_unicode_properties(self):
1277
1274
"""We should be able to round trip a non-ascii property."""
1286
1283
# However, Testaments assert than they are str(), and thus should not
1288
1285
tree.commit('One', rev_id=b'rev1',
1289
revprops={u'omega':u'\u03a9', u'alpha':u'\u03b1'})
1286
revprops={u'omega': u'\u03a9', u'alpha': u'\u03b1'})
1290
1287
self.b1 = tree.branch
1291
1288
bundle_sio, revision_ids = self.create_bundle_text(b'null:', b'rev1')
1292
1289
self.assertContainsRe(bundle_sio.getvalue(),
1294
1291
b'# alpha: \xce\xb1\n'
1295
1292
b'# branch-nick: tree\n'
1296
1293
b'# omega: \xce\xa9\n'
1298
1295
bundle = read_bundle(bundle_sio)
1299
1296
revision_info = bundle.revisions[0]
1300
1297
self.assertEqual(b'rev1', revision_info.revision_id)
1301
1298
rev = revision_info.as_revision()
1302
self.assertEqual({'branch-nick':'tree', 'omega':u'\u03a9',
1303
'alpha':u'\u03b1'}, rev.properties)
1299
self.assertEqual({'branch-nick': 'tree', 'omega': u'\u03a9',
1300
'alpha': u'\u03b1'}, rev.properties)
1306
1303
class V09BundleKnit2Tester(V08BundleTester):
1417
1415
# Turn the 'iterators_of_bytes' back into simple strings for comparison
1418
1416
repo_texts = dict((i, b''.join(content)) for i, content
1419
1417
in target_repo.iter_files_bytes(
1420
[(b'fileid-2', b'rev1', '1'),
1421
(b'fileid-2', b'rev2', '2')]))
1422
self.assertEqual({'1':b'contents1\nstatic\n',
1423
'2':b'contents2\nstatic\n'},
1418
[(b'fileid-2', b'rev1', '1'),
1419
(b'fileid-2', b'rev2', '2')]))
1420
self.assertEqual({'1': b'contents1\nstatic\n',
1421
'2': b'contents2\nstatic\n'},
1425
1423
rtree = target_repo.revision_tree(b'rev2')
1426
1424
inventory_vf = target_repo.inventories
1468
1467
serializer = BundleSerializerV4('4')
1469
1468
with tree_a.lock_read():
1470
serializer.write_bundle(tree_a.branch.repository, b'B', b'null:', s)
1469
serializer.write_bundle(
1470
tree_a.branch.repository, b'B', b'null:', s)
1472
1472
install_bundle(repo_b, serializer.read(s))
1473
1473
self.assertTrue(repo_b.has_signature_for_revision_id(b'B'))
1579
1579
self.assertEqual({b'parents': [],
1580
1580
b'sha1': b'a13f42b142d544aac9b085c42595d304150e31a2',
1581
1581
b'storage_kind': b'mpdiff',
1583
1583
# We should have an mpdiff that takes some lines from both parents.
1584
1584
self.assertEqualDiff(
1586
1586
b'<inventory format="10" revision_id="a@cset-0-1">\n'
1587
1587
b'<directory file_id="root-id" name=""'
1588
b' revision="a@cset-0-1" />\n'
1588
b' revision="a@cset-0-1" />\n'
1589
1589
b'<file file_id="file-id" name="file" parent_id="root-id"'
1590
b' revision="a@cset-0-1"'
1591
b' text_sha1="09c2f8647e14e49e922b955c194102070597c2d1"'
1592
b' text_size="17" />\n'
1590
b' revision="a@cset-0-1"'
1591
b' text_sha1="09c2f8647e14e49e922b955c194102070597c2d1"'
1592
b' text_size="17" />\n'
1593
1593
b'</inventory>\n'
1596
1596
def test_multiple_inventories_as_xml(self):
1597
1597
self.make_merged_branch()
1598
1598
sio = self.make_bundle_just_inventories(b'a@cset-0-1', b'a@cset-0-3',
1599
[b'a@cset-0-2a', b'a@cset-0-2b', b'a@cset-0-3'])
1599
[b'a@cset-0-2a', b'a@cset-0-2b', b'a@cset-0-3'])
1600
1600
reader = v4.BundleReader(sio, stream_input=False)
1601
1601
records = list(reader.iter_records())
1602
1602
self.assertEqual(3, len(records))
1607
1607
self.assertEqual({b'parents': [b'a@cset-0-1'],
1608
1608
b'sha1': b'1e105886d62d510763e22885eec733b66f5f09bf',
1609
1609
b'storage_kind': b'mpdiff',
1611
1611
metadata_2b = records[1][1]
1612
1612
self.assertEqual({b'parents': [b'a@cset-0-1'],
1613
1613
b'sha1': b'f03f12574bdb5ed2204c28636c98a8547544ccd8',
1614
1614
b'storage_kind': b'mpdiff',
1616
1616
metadata_3 = records[2][1]
1617
1617
self.assertEqual({b'parents': [b'a@cset-0-2a', b'a@cset-0-2b'],
1618
1618
b'sha1': b'09c53b0c4de0895e11a2aacc34fef60a6e70865c',
1619
1619
b'storage_kind': b'mpdiff',
1621
1621
bytes_2a = records[0][0]
1622
1622
self.assertEqualDiff(
1655
1655
def test_creating_bundle_preserves_chk_pages(self):
1656
1656
self.make_merged_branch()
1657
1657
target = self.b1.controldir.sprout('target',
1658
revision_id=b'a@cset-0-2a').open_branch()
1658
revision_id=b'a@cset-0-2a').open_branch()
1659
1659
bundle_txt, rev_ids = self.create_bundle_text(b'a@cset-0-2a',
1661
1661
self.assertEqual(set([b'a@cset-0-2b', b'a@cset-0-3']), set(rev_ids))
1695
1695
def check_valid(self, bundle):
1696
1696
"""Check that after whatever munging, the final object is valid."""
1697
1697
self.assertEqual([b'a@cset-0-2'],
1698
[r.revision_id for r in bundle.real_revisions])
1698
[r.revision_id for r in bundle.real_revisions])
1700
1700
def test_extra_whitespace(self):
1701
1701
bundle_txt = self.build_test_bundle()
1776
1776
writer.add_info_record({b'foo': b'bar'})
1777
1777
writer._add_record(b"Record body", {b'parents': [b'1', b'3'],
1778
b'storage_kind': b'fulltext'}, 'file', b'revid', b'fileid')
1778
b'storage_kind': b'fulltext'}, 'file', b'revid', b'fileid')
1780
1780
fileobj.seek(0)
1781
1781
reader = v4.BundleReader(fileobj, stream_input=True)
1782
1782
record_iter = reader.iter_records()
1783
1783
record = next(record_iter)
1784
1784
self.assertEqual((None, {b'foo': b'bar', b'storage_kind': b'header'},
1785
'info', None, None), record)
1785
'info', None, None), record)
1786
1786
record = next(record_iter)
1787
1787
self.assertEqual((b"Record body", {b'storage_kind': b'fulltext',
1788
b'parents': [b'1', b'3']}, 'file', b'revid', b'fileid'),
1788
b'parents': [b'1', b'3']}, 'file', b'revid', b'fileid'),
1791
1791
def test_roundtrip_record_memory_hungry(self):
1792
1792
fileobj = BytesIO()
1795
1795
writer.add_info_record({b'foo': b'bar'})
1796
1796
writer._add_record(b"Record body", {b'parents': [b'1', b'3'],
1797
b'storage_kind': b'fulltext'}, 'file', b'revid', b'fileid')
1797
b'storage_kind': b'fulltext'}, 'file', b'revid', b'fileid')
1799
1799
fileobj.seek(0)
1800
1800
reader = v4.BundleReader(fileobj, stream_input=False)
1801
1801
record_iter = reader.iter_records()
1802
1802
record = next(record_iter)
1803
1803
self.assertEqual((None, {b'foo': b'bar', b'storage_kind': b'header'},
1804
'info', None, None), record)
1804
'info', None, None), record)
1805
1805
record = next(record_iter)
1806
1806
self.assertEqual((b"Record body", {b'storage_kind': b'fulltext',
1807
b'parents': [b'1', b'3']}, 'file', b'revid', b'fileid'),
1807
b'parents': [b'1', b'3']}, 'file', b'revid', b'fileid'),
1810
1810
def test_encode_name(self):
1811
1811
self.assertEqual(b'revision/rev1',
1812
v4.BundleWriter.encode_name('revision', b'rev1'))
1812
v4.BundleWriter.encode_name('revision', b'rev1'))
1813
1813
self.assertEqual(b'file/rev//1/file-id-1',
1814
v4.BundleWriter.encode_name('file', b'rev/1', b'file-id-1'))
1814
v4.BundleWriter.encode_name('file', b'rev/1', b'file-id-1'))
1815
1815
self.assertEqual(b'info',
1816
v4.BundleWriter.encode_name('info', None, None))
1816
v4.BundleWriter.encode_name('info', None, None))
1818
1818
def test_decode_name(self):
1819
1819
self.assertEqual(('revision', b'rev1', None),
1820
v4.BundleReader.decode_name(b'revision/rev1'))
1820
v4.BundleReader.decode_name(b'revision/rev1'))
1821
1821
self.assertEqual(('file', b'rev/1', b'file-id-1'),
1822
v4.BundleReader.decode_name(b'file/rev//1/file-id-1'))
1822
v4.BundleReader.decode_name(b'file/rev//1/file-id-1'))
1823
1823
self.assertEqual(('info', None, None),
1824
1824
v4.BundleReader.decode_name(b'info'))
1834
1834
record_iter = v4.BundleReader(fileobj).iter_records()
1835
1835
record = next(record_iter)
1836
1836
self.assertEqual((None, {b'foo': b'bar', b'storage_kind': b'header'},
1837
'info', None, None), record)
1837
'info', None, None), record)
1838
1838
self.assertRaises(errors.BadBundle, next, record_iter)