test_bundle.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. # test_bundle.py -- tests for bundle
  2. # Copyright (C) 2020 Jelmer Vernooij <jelmer@jelmer.uk>
  3. #
  4. # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
  5. # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
  6. # General Public License as published by the Free Software Foundation; version 2.0
  7. # or (at your option) any later version. You can redistribute it and/or
  8. # modify it under the terms of either of these two licenses.
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. #
  16. # You should have received a copy of the licenses; if not, see
  17. # <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
  18. # and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
  19. # License, Version 2.0.
  20. #
  21. """Tests for bundle support."""
  22. import os
  23. import tempfile
  24. from io import BytesIO
  25. from dulwich.bundle import Bundle, create_bundle_from_repo, read_bundle, write_bundle
  26. from dulwich.object_format import DEFAULT_OBJECT_FORMAT
  27. from dulwich.objects import Blob, Commit, Tree
  28. from dulwich.pack import PackData, write_pack_objects
  29. from dulwich.repo import MemoryRepo
  30. from . import TestCase
  31. class BundleTests(TestCase):
  32. def setUp(self):
  33. super().setUp()
  34. self.tempdir = tempfile.mkdtemp()
  35. self.addCleanup(os.rmdir, self.tempdir)
  36. def test_bundle_repr(self) -> None:
  37. """Test the Bundle.__repr__ method."""
  38. bundle = Bundle()
  39. self.addCleanup(bundle.close)
  40. bundle.version = 3
  41. bundle.capabilities = {"foo": "bar"}
  42. bundle.prerequisites = [(b"cc" * 20, "comment")]
  43. bundle.references = {b"refs/heads/master": b"ab" * 20}
  44. # Create a simple pack data
  45. b = BytesIO()
  46. write_pack_objects(b.write, [], object_format=DEFAULT_OBJECT_FORMAT)
  47. b.seek(0)
  48. bundle.pack_data = PackData.from_file(b, object_format=DEFAULT_OBJECT_FORMAT)
  49. self.addCleanup(bundle.pack_data.close)
  50. # Check the repr output
  51. rep = repr(bundle)
  52. self.assertIn("Bundle(version=3", rep)
  53. self.assertIn("capabilities={'foo': 'bar'}", rep)
  54. self.assertIn("prerequisites=[(", rep)
  55. self.assertIn("references={", rep)
  56. def test_bundle_equality(self) -> None:
  57. """Test the Bundle.__eq__ method."""
  58. # Create two identical bundles
  59. bundle1 = Bundle()
  60. self.addCleanup(bundle1.close)
  61. bundle1.version = 3
  62. bundle1.capabilities = {"foo": "bar"}
  63. bundle1.prerequisites = [(b"cc" * 20, "comment")]
  64. bundle1.references = {b"refs/heads/master": b"ab" * 20}
  65. b1 = BytesIO()
  66. write_pack_objects(b1.write, [], object_format=DEFAULT_OBJECT_FORMAT)
  67. b1.seek(0)
  68. bundle1.pack_data = PackData.from_file(b1, object_format=DEFAULT_OBJECT_FORMAT)
  69. bundle2 = Bundle()
  70. self.addCleanup(bundle2.close)
  71. bundle2.version = 3
  72. bundle2.capabilities = {"foo": "bar"}
  73. bundle2.prerequisites = [(b"cc" * 20, "comment")]
  74. bundle2.references = {b"refs/heads/master": b"ab" * 20}
  75. b2 = BytesIO()
  76. write_pack_objects(b2.write, [], object_format=DEFAULT_OBJECT_FORMAT)
  77. b2.seek(0)
  78. bundle2.pack_data = PackData.from_file(b2, object_format=DEFAULT_OBJECT_FORMAT)
  79. # Test equality
  80. self.assertEqual(bundle1, bundle2)
  81. # Test inequality by changing different attributes
  82. bundle3 = Bundle()
  83. self.addCleanup(bundle3.close)
  84. bundle3.version = 2 # Different version
  85. bundle3.capabilities = {"foo": "bar"}
  86. bundle3.prerequisites = [(b"cc" * 20, "comment")]
  87. bundle3.references = {b"refs/heads/master": b"ab" * 20}
  88. b3 = BytesIO()
  89. write_pack_objects(b3.write, [], object_format=DEFAULT_OBJECT_FORMAT)
  90. b3.seek(0)
  91. bundle3.pack_data = PackData.from_file(b3, object_format=DEFAULT_OBJECT_FORMAT)
  92. self.assertNotEqual(bundle1, bundle3)
  93. bundle4 = Bundle()
  94. self.addCleanup(bundle4.close)
  95. bundle4.version = 3
  96. bundle4.capabilities = {"different": "value"} # Different capabilities
  97. bundle4.prerequisites = [(b"cc" * 20, "comment")]
  98. bundle4.references = {b"refs/heads/master": b"ab" * 20}
  99. b4 = BytesIO()
  100. write_pack_objects(b4.write, [], object_format=DEFAULT_OBJECT_FORMAT)
  101. b4.seek(0)
  102. bundle4.pack_data = PackData.from_file(b4, object_format=DEFAULT_OBJECT_FORMAT)
  103. self.assertNotEqual(bundle1, bundle4)
  104. bundle5 = Bundle()
  105. self.addCleanup(bundle5.close)
  106. bundle5.version = 3
  107. bundle5.capabilities = {"foo": "bar"}
  108. bundle5.prerequisites = [(b"dd" * 20, "different")] # Different prerequisites
  109. bundle5.references = {b"refs/heads/master": b"ab" * 20}
  110. b5 = BytesIO()
  111. write_pack_objects(b5.write, [], object_format=DEFAULT_OBJECT_FORMAT)
  112. b5.seek(0)
  113. bundle5.pack_data = PackData.from_file(b5, object_format=DEFAULT_OBJECT_FORMAT)
  114. self.assertNotEqual(bundle1, bundle5)
  115. bundle6 = Bundle()
  116. self.addCleanup(bundle6.close)
  117. bundle6.version = 3
  118. bundle6.capabilities = {"foo": "bar"}
  119. bundle6.prerequisites = [(b"cc" * 20, "comment")]
  120. bundle6.references = {
  121. b"refs/heads/different": b"ab" * 20
  122. } # Different references
  123. b6 = BytesIO()
  124. write_pack_objects(b6.write, [], object_format=DEFAULT_OBJECT_FORMAT)
  125. b6.seek(0)
  126. bundle6.pack_data = PackData.from_file(b6, object_format=DEFAULT_OBJECT_FORMAT)
  127. self.assertNotEqual(bundle1, bundle6)
  128. # Test inequality with different type
  129. self.assertNotEqual(bundle1, "not a bundle")
  130. def test_read_bundle_v2(self) -> None:
  131. """Test reading a v2 bundle."""
  132. f = BytesIO()
  133. f.write(b"# v2 git bundle\n")
  134. f.write(b"-" + b"cc" * 20 + b" prerequisite comment\n")
  135. f.write(b"ab" * 20 + b" refs/heads/master\n")
  136. f.write(b"\n")
  137. # Add pack data
  138. b = BytesIO()
  139. write_pack_objects(b.write, [], object_format=DEFAULT_OBJECT_FORMAT)
  140. f.write(b.getvalue())
  141. f.seek(0)
  142. bundle = read_bundle(f)
  143. self.addCleanup(bundle.close)
  144. self.assertEqual(2, bundle.version)
  145. self.assertEqual({}, bundle.capabilities)
  146. self.assertEqual([(b"cc" * 20, b"prerequisite comment")], bundle.prerequisites)
  147. self.assertEqual({b"refs/heads/master": b"ab" * 20}, bundle.references)
  148. def test_read_bundle_v3(self) -> None:
  149. """Test reading a v3 bundle with capabilities."""
  150. f = BytesIO()
  151. f.write(b"# v3 git bundle\n")
  152. f.write(b"@capability1\n")
  153. f.write(b"@capability2=value2\n")
  154. f.write(b"-" + b"cc" * 20 + b" prerequisite comment\n")
  155. f.write(b"ab" * 20 + b" refs/heads/master\n")
  156. f.write(b"\n")
  157. # Add pack data
  158. b = BytesIO()
  159. write_pack_objects(b.write, [], object_format=DEFAULT_OBJECT_FORMAT)
  160. f.write(b.getvalue())
  161. f.seek(0)
  162. bundle = read_bundle(f)
  163. self.addCleanup(bundle.close)
  164. self.assertEqual(3, bundle.version)
  165. self.assertEqual(
  166. {"capability1": None, "capability2": "value2"}, bundle.capabilities
  167. )
  168. self.assertEqual([(b"cc" * 20, b"prerequisite comment")], bundle.prerequisites)
  169. self.assertEqual({b"refs/heads/master": b"ab" * 20}, bundle.references)
  170. def test_read_bundle_invalid_format(self) -> None:
  171. """Test reading a bundle with invalid format."""
  172. f = BytesIO()
  173. f.write(b"invalid bundle format\n")
  174. f.seek(0)
  175. with self.assertRaises(AssertionError):
  176. read_bundle(f)
  177. def test_write_bundle_v2(self) -> None:
  178. """Test writing a v2 bundle."""
  179. bundle = Bundle()
  180. self.addCleanup(bundle.close)
  181. bundle.version = 2
  182. bundle.capabilities = {}
  183. bundle.prerequisites = [(b"cc" * 20, b"prerequisite comment")]
  184. bundle.references = {b"refs/heads/master": b"ab" * 20}
  185. # Create a simple pack data
  186. b = BytesIO()
  187. write_pack_objects(b.write, [], object_format=DEFAULT_OBJECT_FORMAT)
  188. b.seek(0)
  189. bundle.pack_data = PackData.from_file(b, object_format=DEFAULT_OBJECT_FORMAT)
  190. # Write the bundle
  191. f = BytesIO()
  192. write_bundle(f, bundle)
  193. f.seek(0)
  194. # Verify the written content
  195. self.assertEqual(b"# v2 git bundle\n", f.readline())
  196. self.assertEqual(b"-" + b"cc" * 20 + b" prerequisite comment\n", f.readline())
  197. self.assertEqual(b"ab" * 20 + b" refs/heads/master\n", f.readline())
  198. self.assertEqual(b"\n", f.readline())
  199. # The rest is pack data which we don't validate in detail
  200. def test_write_bundle_v3(self) -> None:
  201. """Test writing a v3 bundle with capabilities."""
  202. bundle = Bundle()
  203. self.addCleanup(bundle.close)
  204. bundle.version = 3
  205. bundle.capabilities = {"capability1": None, "capability2": "value2"}
  206. bundle.prerequisites = [(b"cc" * 20, b"prerequisite comment")]
  207. bundle.references = {b"refs/heads/master": b"ab" * 20}
  208. # Create a simple pack data
  209. b = BytesIO()
  210. write_pack_objects(b.write, [], object_format=DEFAULT_OBJECT_FORMAT)
  211. b.seek(0)
  212. bundle.pack_data = PackData.from_file(b, object_format=DEFAULT_OBJECT_FORMAT)
  213. # Write the bundle
  214. f = BytesIO()
  215. write_bundle(f, bundle)
  216. f.seek(0)
  217. # Verify the written content
  218. self.assertEqual(b"# v3 git bundle\n", f.readline())
  219. self.assertEqual(b"@capability1\n", f.readline())
  220. self.assertEqual(b"@capability2=value2\n", f.readline())
  221. self.assertEqual(b"-" + b"cc" * 20 + b" prerequisite comment\n", f.readline())
  222. self.assertEqual(b"ab" * 20 + b" refs/heads/master\n", f.readline())
  223. self.assertEqual(b"\n", f.readline())
  224. # The rest is pack data which we don't validate in detail
  225. def test_write_bundle_auto_version(self) -> None:
  226. """Test writing a bundle with auto-detected version."""
  227. # Create a bundle with no explicit version but capabilities
  228. bundle1 = Bundle()
  229. self.addCleanup(bundle1.close)
  230. bundle1.version = None
  231. bundle1.capabilities = {"capability1": "value1"}
  232. bundle1.prerequisites = [(b"cc" * 20, b"prerequisite comment")]
  233. bundle1.references = {b"refs/heads/master": b"ab" * 20}
  234. b1 = BytesIO()
  235. write_pack_objects(b1.write, [], object_format=DEFAULT_OBJECT_FORMAT)
  236. b1.seek(0)
  237. bundle1.pack_data = PackData.from_file(b1, object_format=DEFAULT_OBJECT_FORMAT)
  238. f1 = BytesIO()
  239. write_bundle(f1, bundle1)
  240. f1.seek(0)
  241. # Should use v3 format since capabilities are present
  242. self.assertEqual(b"# v3 git bundle\n", f1.readline())
  243. # Create a bundle with no explicit version and no capabilities
  244. bundle2 = Bundle()
  245. self.addCleanup(bundle2.close)
  246. bundle2.version = None
  247. bundle2.capabilities = {}
  248. bundle2.prerequisites = [(b"cc" * 20, b"prerequisite comment")]
  249. bundle2.references = {b"refs/heads/master": b"ab" * 20}
  250. b2 = BytesIO()
  251. write_pack_objects(b2.write, [], object_format=DEFAULT_OBJECT_FORMAT)
  252. b2.seek(0)
  253. bundle2.pack_data = PackData.from_file(b2, object_format=DEFAULT_OBJECT_FORMAT)
  254. f2 = BytesIO()
  255. write_bundle(f2, bundle2)
  256. f2.seek(0)
  257. # Should use v2 format since no capabilities are present
  258. self.assertEqual(b"# v2 git bundle\n", f2.readline())
  259. def test_write_bundle_invalid_version(self) -> None:
  260. """Test writing a bundle with an invalid version."""
  261. bundle = Bundle()
  262. self.addCleanup(bundle.close)
  263. bundle.version = 4 # Invalid version
  264. bundle.capabilities = {}
  265. bundle.prerequisites = []
  266. bundle.references = {}
  267. b = BytesIO()
  268. write_pack_objects(b.write, [], object_format=DEFAULT_OBJECT_FORMAT)
  269. b.seek(0)
  270. bundle.pack_data = PackData.from_file(b, object_format=DEFAULT_OBJECT_FORMAT)
  271. f = BytesIO()
  272. with self.assertRaises(AssertionError):
  273. write_bundle(f, bundle)
  274. def test_roundtrip_bundle(self) -> None:
  275. origbundle = Bundle()
  276. self.addCleanup(origbundle.close)
  277. origbundle.version = 3
  278. origbundle.capabilities = {"foo": None}
  279. origbundle.references = {b"refs/heads/master": b"ab" * 20}
  280. origbundle.prerequisites = [(b"cc" * 20, b"comment")]
  281. b = BytesIO()
  282. write_pack_objects(b.write, [], object_format=DEFAULT_OBJECT_FORMAT)
  283. b.seek(0)
  284. origbundle.pack_data = PackData.from_file(
  285. b, object_format=DEFAULT_OBJECT_FORMAT
  286. )
  287. with tempfile.TemporaryDirectory() as td:
  288. with open(os.path.join(td, "foo"), "wb") as f:
  289. write_bundle(f, origbundle)
  290. with open(os.path.join(td, "foo"), "rb") as f:
  291. newbundle = read_bundle(f)
  292. self.addCleanup(newbundle.close)
  293. self.assertEqual(origbundle, newbundle)
  294. def test_create_bundle_from_repo(self) -> None:
  295. """Test creating a bundle from a repository."""
  296. # Create a simple repository
  297. repo = MemoryRepo()
  298. self.addCleanup(repo.close)
  299. # Create a blob
  300. blob = Blob.from_string(b"Hello world")
  301. repo.object_store.add_object(blob)
  302. # Create a tree
  303. tree = Tree()
  304. tree.add(b"hello.txt", 0o100644, blob.id)
  305. repo.object_store.add_object(tree)
  306. # Create a commit
  307. commit = Commit()
  308. commit.tree = tree.id
  309. commit.message = b"Initial commit"
  310. commit.author = commit.committer = b"Test User <test@example.com>"
  311. commit.commit_time = commit.author_time = 1234567890
  312. commit.commit_timezone = commit.author_timezone = 0
  313. repo.object_store.add_object(commit)
  314. # Add a reference
  315. repo.refs[b"refs/heads/master"] = commit.id
  316. # Create bundle from repository
  317. bundle = create_bundle_from_repo(repo)
  318. self.addCleanup(bundle.close)
  319. # Verify bundle contents
  320. self.assertEqual(bundle.references, {b"refs/heads/master": commit.id})
  321. self.assertEqual(bundle.prerequisites, [])
  322. self.assertEqual(bundle.capabilities, {})
  323. self.assertIsNotNone(bundle.pack_data)
  324. # Verify the bundle contains the right objects
  325. objects = list(bundle.pack_data.iter_unpacked())
  326. object_ids = {obj.sha().hex().encode("ascii") for obj in objects}
  327. self.assertIn(blob.id, object_ids)
  328. self.assertIn(tree.id, object_ids)
  329. self.assertIn(commit.id, object_ids)
  330. def test_create_bundle_with_prerequisites(self) -> None:
  331. """Test creating a bundle with prerequisites."""
  332. repo = MemoryRepo()
  333. # Create some objects
  334. blob = Blob.from_string(b"Hello world")
  335. repo.object_store.add_object(blob)
  336. tree = Tree()
  337. tree.add(b"hello.txt", 0o100644, blob.id)
  338. repo.object_store.add_object(tree)
  339. commit = Commit()
  340. commit.tree = tree.id
  341. commit.message = b"Initial commit"
  342. commit.author = commit.committer = b"Test User <test@example.com>"
  343. commit.commit_time = commit.author_time = 1234567890
  344. commit.commit_timezone = commit.author_timezone = 0
  345. repo.object_store.add_object(commit)
  346. repo.refs[b"refs/heads/master"] = commit.id
  347. # Create bundle with prerequisites
  348. prereq_id = b"aa" * 20 # hex string like other object ids
  349. bundle = create_bundle_from_repo(repo, prerequisites=[prereq_id])
  350. self.addCleanup(bundle.close)
  351. # Verify prerequisites are included
  352. self.assertEqual(len(bundle.prerequisites), 1)
  353. self.assertEqual(bundle.prerequisites[0][0], prereq_id)
  354. def test_create_bundle_with_specific_refs(self) -> None:
  355. """Test creating a bundle with specific refs."""
  356. repo = MemoryRepo()
  357. # Create objects and refs
  358. blob = Blob.from_string(b"Hello world")
  359. repo.object_store.add_object(blob)
  360. tree = Tree()
  361. tree.add(b"hello.txt", 0o100644, blob.id)
  362. repo.object_store.add_object(tree)
  363. commit = Commit()
  364. commit.tree = tree.id
  365. commit.message = b"Initial commit"
  366. commit.author = commit.committer = b"Test User <test@example.com>"
  367. commit.commit_time = commit.author_time = 1234567890
  368. commit.commit_timezone = commit.author_timezone = 0
  369. repo.object_store.add_object(commit)
  370. repo.refs[b"refs/heads/master"] = commit.id
  371. repo.refs[b"refs/heads/feature"] = commit.id
  372. # Create bundle with only master ref
  373. from dulwich.refs import Ref
  374. bundle = create_bundle_from_repo(repo, refs=[Ref(b"refs/heads/master")])
  375. self.addCleanup(bundle.close)
  376. # Verify only master ref is included
  377. self.assertEqual(len(bundle.references), 1)
  378. self.assertIn(b"refs/heads/master", bundle.references)
  379. self.assertNotIn(b"refs/heads/feature", bundle.references)
  380. def test_create_bundle_with_capabilities(self) -> None:
  381. """Test creating a bundle with capabilities."""
  382. repo = MemoryRepo()
  383. # Create minimal objects
  384. blob = Blob.from_string(b"Hello world")
  385. repo.object_store.add_object(blob)
  386. tree = Tree()
  387. tree.add(b"hello.txt", 0o100644, blob.id)
  388. repo.object_store.add_object(tree)
  389. commit = Commit()
  390. commit.tree = tree.id
  391. commit.message = b"Initial commit"
  392. commit.author = commit.committer = b"Test User <test@example.com>"
  393. commit.commit_time = commit.author_time = 1234567890
  394. commit.commit_timezone = commit.author_timezone = 0
  395. repo.object_store.add_object(commit)
  396. repo.refs[b"refs/heads/master"] = commit.id
  397. # Create bundle with capabilities
  398. capabilities = {"object-format": "sha1"}
  399. bundle = create_bundle_from_repo(repo, capabilities=capabilities, version=3)
  400. self.addCleanup(bundle.close)
  401. # Verify capabilities are included
  402. self.assertEqual(bundle.capabilities, capabilities)
  403. self.assertEqual(bundle.version, 3)
  404. def test_create_bundle_with_hex_bytestring_prerequisite(self) -> None:
  405. """Test creating a bundle with prerequisite as 40-byte hex bytestring."""
  406. repo = MemoryRepo()
  407. # Create minimal objects
  408. blob = Blob.from_string(b"Hello world")
  409. repo.object_store.add_object(blob)
  410. tree = Tree()
  411. tree.add(b"hello.txt", 0o100644, blob.id)
  412. repo.object_store.add_object(tree)
  413. commit = Commit()
  414. commit.tree = tree.id
  415. commit.message = b"Initial commit"
  416. commit.author = commit.committer = b"Test User <test@example.com>"
  417. commit.commit_time = commit.author_time = 1234567890
  418. commit.commit_timezone = commit.author_timezone = 0
  419. repo.object_store.add_object(commit)
  420. repo.refs[b"refs/heads/master"] = commit.id
  421. # Create another blob to use as prerequisite
  422. prereq_blob = Blob.from_string(b"prerequisite")
  423. # Use blob.id directly (40-byte hex bytestring)
  424. bundle = create_bundle_from_repo(repo, prerequisites=[prereq_blob.id])
  425. self.addCleanup(bundle.close)
  426. # Verify the prerequisite was added correctly
  427. self.assertEqual(len(bundle.prerequisites), 1)
  428. self.assertEqual(bundle.prerequisites[0][0], prereq_blob.id)
  429. def test_create_bundle_with_hex_bytestring_prerequisite_simple(self) -> None:
  430. """Test creating a bundle with prerequisite as 40-byte hex bytestring."""
  431. repo = MemoryRepo()
  432. # Create minimal objects
  433. blob = Blob.from_string(b"Hello world")
  434. repo.object_store.add_object(blob)
  435. tree = Tree()
  436. tree.add(b"hello.txt", 0o100644, blob.id)
  437. repo.object_store.add_object(tree)
  438. commit = Commit()
  439. commit.tree = tree.id
  440. commit.message = b"Initial commit"
  441. commit.author = commit.committer = b"Test User <test@example.com>"
  442. commit.commit_time = commit.author_time = 1234567890
  443. commit.commit_timezone = commit.author_timezone = 0
  444. repo.object_store.add_object(commit)
  445. repo.refs[b"refs/heads/master"] = commit.id
  446. # Use a 40-byte hex bytestring as prerequisite
  447. prereq_hex = b"aa" * 20
  448. bundle = create_bundle_from_repo(repo, prerequisites=[prereq_hex])
  449. self.addCleanup(bundle.close)
  450. # Verify the prerequisite was added correctly
  451. self.assertEqual(len(bundle.prerequisites), 1)
  452. self.assertEqual(bundle.prerequisites[0][0], prereq_hex)