test_bundle.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  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 public 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, read_bundle, write_bundle
  26. from dulwich.pack import PackData, write_pack_objects
  27. from . import TestCase
  28. class BundleTests(TestCase):
  29. def setUp(self):
  30. super().setUp()
  31. self.tempdir = tempfile.mkdtemp()
  32. self.addCleanup(os.rmdir, self.tempdir)
  33. def test_bundle_repr(self) -> None:
  34. """Test the Bundle.__repr__ method."""
  35. bundle = Bundle()
  36. bundle.version = 3
  37. bundle.capabilities = {"foo": "bar"}
  38. bundle.prerequisites = [(b"cc" * 20, "comment")]
  39. bundle.references = {b"refs/heads/master": b"ab" * 20}
  40. # Create a simple pack data
  41. b = BytesIO()
  42. write_pack_objects(b.write, [])
  43. b.seek(0)
  44. bundle.pack_data = PackData.from_file(b)
  45. # Check the repr output
  46. rep = repr(bundle)
  47. self.assertIn("Bundle(version=3", rep)
  48. self.assertIn("capabilities={'foo': 'bar'}", rep)
  49. self.assertIn("prerequisites=[(", rep)
  50. self.assertIn("references={", rep)
  51. def test_bundle_equality(self) -> None:
  52. """Test the Bundle.__eq__ method."""
  53. # Create two identical bundles
  54. bundle1 = Bundle()
  55. bundle1.version = 3
  56. bundle1.capabilities = {"foo": "bar"}
  57. bundle1.prerequisites = [(b"cc" * 20, "comment")]
  58. bundle1.references = {b"refs/heads/master": b"ab" * 20}
  59. b1 = BytesIO()
  60. write_pack_objects(b1.write, [])
  61. b1.seek(0)
  62. bundle1.pack_data = PackData.from_file(b1)
  63. bundle2 = Bundle()
  64. bundle2.version = 3
  65. bundle2.capabilities = {"foo": "bar"}
  66. bundle2.prerequisites = [(b"cc" * 20, "comment")]
  67. bundle2.references = {b"refs/heads/master": b"ab" * 20}
  68. b2 = BytesIO()
  69. write_pack_objects(b2.write, [])
  70. b2.seek(0)
  71. bundle2.pack_data = PackData.from_file(b2)
  72. # Test equality
  73. self.assertEqual(bundle1, bundle2)
  74. # Test inequality by changing different attributes
  75. bundle3 = Bundle()
  76. bundle3.version = 2 # Different version
  77. bundle3.capabilities = {"foo": "bar"}
  78. bundle3.prerequisites = [(b"cc" * 20, "comment")]
  79. bundle3.references = {b"refs/heads/master": b"ab" * 20}
  80. b3 = BytesIO()
  81. write_pack_objects(b3.write, [])
  82. b3.seek(0)
  83. bundle3.pack_data = PackData.from_file(b3)
  84. self.assertNotEqual(bundle1, bundle3)
  85. bundle4 = Bundle()
  86. bundle4.version = 3
  87. bundle4.capabilities = {"different": "value"} # Different capabilities
  88. bundle4.prerequisites = [(b"cc" * 20, "comment")]
  89. bundle4.references = {b"refs/heads/master": b"ab" * 20}
  90. b4 = BytesIO()
  91. write_pack_objects(b4.write, [])
  92. b4.seek(0)
  93. bundle4.pack_data = PackData.from_file(b4)
  94. self.assertNotEqual(bundle1, bundle4)
  95. bundle5 = Bundle()
  96. bundle5.version = 3
  97. bundle5.capabilities = {"foo": "bar"}
  98. bundle5.prerequisites = [(b"dd" * 20, "different")] # Different prerequisites
  99. bundle5.references = {b"refs/heads/master": b"ab" * 20}
  100. b5 = BytesIO()
  101. write_pack_objects(b5.write, [])
  102. b5.seek(0)
  103. bundle5.pack_data = PackData.from_file(b5)
  104. self.assertNotEqual(bundle1, bundle5)
  105. bundle6 = Bundle()
  106. bundle6.version = 3
  107. bundle6.capabilities = {"foo": "bar"}
  108. bundle6.prerequisites = [(b"cc" * 20, "comment")]
  109. bundle6.references = {
  110. b"refs/heads/different": b"ab" * 20
  111. } # Different references
  112. b6 = BytesIO()
  113. write_pack_objects(b6.write, [])
  114. b6.seek(0)
  115. bundle6.pack_data = PackData.from_file(b6)
  116. self.assertNotEqual(bundle1, bundle6)
  117. # Test inequality with different type
  118. self.assertNotEqual(bundle1, "not a bundle")
  119. def test_read_bundle_v2(self) -> None:
  120. """Test reading a v2 bundle."""
  121. f = BytesIO()
  122. f.write(b"# v2 git bundle\n")
  123. f.write(b"-" + b"cc" * 20 + b" prerequisite comment\n")
  124. f.write(b"ab" * 20 + b" refs/heads/master\n")
  125. f.write(b"\n")
  126. # Add pack data
  127. b = BytesIO()
  128. write_pack_objects(b.write, [])
  129. f.write(b.getvalue())
  130. f.seek(0)
  131. bundle = read_bundle(f)
  132. self.assertEqual(2, bundle.version)
  133. self.assertEqual({}, bundle.capabilities)
  134. self.assertEqual([(b"cc" * 20, "prerequisite comment")], bundle.prerequisites)
  135. self.assertEqual({b"refs/heads/master": b"ab" * 20}, bundle.references)
  136. def test_read_bundle_v3(self) -> None:
  137. """Test reading a v3 bundle with capabilities."""
  138. f = BytesIO()
  139. f.write(b"# v3 git bundle\n")
  140. f.write(b"@capability1\n")
  141. f.write(b"@capability2=value2\n")
  142. f.write(b"-" + b"cc" * 20 + b" prerequisite comment\n")
  143. f.write(b"ab" * 20 + b" refs/heads/master\n")
  144. f.write(b"\n")
  145. # Add pack data
  146. b = BytesIO()
  147. write_pack_objects(b.write, [])
  148. f.write(b.getvalue())
  149. f.seek(0)
  150. bundle = read_bundle(f)
  151. self.assertEqual(3, bundle.version)
  152. self.assertEqual(
  153. {"capability1": None, "capability2": "value2"}, bundle.capabilities
  154. )
  155. self.assertEqual([(b"cc" * 20, "prerequisite comment")], bundle.prerequisites)
  156. self.assertEqual({b"refs/heads/master": b"ab" * 20}, bundle.references)
  157. def test_read_bundle_invalid_format(self) -> None:
  158. """Test reading a bundle with invalid format."""
  159. f = BytesIO()
  160. f.write(b"invalid bundle format\n")
  161. f.seek(0)
  162. with self.assertRaises(AssertionError):
  163. read_bundle(f)
  164. def test_write_bundle_v2(self) -> None:
  165. """Test writing a v2 bundle."""
  166. bundle = Bundle()
  167. bundle.version = 2
  168. bundle.capabilities = {}
  169. bundle.prerequisites = [(b"cc" * 20, "prerequisite comment")]
  170. bundle.references = {b"refs/heads/master": b"ab" * 20}
  171. # Create a simple pack data
  172. b = BytesIO()
  173. write_pack_objects(b.write, [])
  174. b.seek(0)
  175. bundle.pack_data = PackData.from_file(b)
  176. # Write the bundle
  177. f = BytesIO()
  178. write_bundle(f, bundle)
  179. f.seek(0)
  180. # Verify the written content
  181. self.assertEqual(b"# v2 git bundle\n", f.readline())
  182. self.assertEqual(b"-" + b"cc" * 20 + b" prerequisite comment\n", f.readline())
  183. self.assertEqual(b"ab" * 20 + b" refs/heads/master\n", f.readline())
  184. self.assertEqual(b"\n", f.readline())
  185. # The rest is pack data which we don't validate in detail
  186. def test_write_bundle_v3(self) -> None:
  187. """Test writing a v3 bundle with capabilities."""
  188. bundle = Bundle()
  189. bundle.version = 3
  190. bundle.capabilities = {"capability1": None, "capability2": "value2"}
  191. bundle.prerequisites = [(b"cc" * 20, "prerequisite comment")]
  192. bundle.references = {b"refs/heads/master": b"ab" * 20}
  193. # Create a simple pack data
  194. b = BytesIO()
  195. write_pack_objects(b.write, [])
  196. b.seek(0)
  197. bundle.pack_data = PackData.from_file(b)
  198. # Write the bundle
  199. f = BytesIO()
  200. write_bundle(f, bundle)
  201. f.seek(0)
  202. # Verify the written content
  203. self.assertEqual(b"# v3 git bundle\n", f.readline())
  204. self.assertEqual(b"@capability1\n", f.readline())
  205. self.assertEqual(b"@capability2=value2\n", f.readline())
  206. self.assertEqual(b"-" + b"cc" * 20 + b" prerequisite comment\n", f.readline())
  207. self.assertEqual(b"ab" * 20 + b" refs/heads/master\n", f.readline())
  208. self.assertEqual(b"\n", f.readline())
  209. # The rest is pack data which we don't validate in detail
  210. def test_write_bundle_auto_version(self) -> None:
  211. """Test writing a bundle with auto-detected version."""
  212. # Create a bundle with no explicit version but capabilities
  213. bundle1 = Bundle()
  214. bundle1.version = None
  215. bundle1.capabilities = {"capability1": "value1"}
  216. bundle1.prerequisites = [(b"cc" * 20, "prerequisite comment")]
  217. bundle1.references = {b"refs/heads/master": b"ab" * 20}
  218. b1 = BytesIO()
  219. write_pack_objects(b1.write, [])
  220. b1.seek(0)
  221. bundle1.pack_data = PackData.from_file(b1)
  222. f1 = BytesIO()
  223. write_bundle(f1, bundle1)
  224. f1.seek(0)
  225. # Should use v3 format since capabilities are present
  226. self.assertEqual(b"# v3 git bundle\n", f1.readline())
  227. # Create a bundle with no explicit version and no capabilities
  228. bundle2 = Bundle()
  229. bundle2.version = None
  230. bundle2.capabilities = {}
  231. bundle2.prerequisites = [(b"cc" * 20, "prerequisite comment")]
  232. bundle2.references = {b"refs/heads/master": b"ab" * 20}
  233. b2 = BytesIO()
  234. write_pack_objects(b2.write, [])
  235. b2.seek(0)
  236. bundle2.pack_data = PackData.from_file(b2)
  237. f2 = BytesIO()
  238. write_bundle(f2, bundle2)
  239. f2.seek(0)
  240. # Should use v2 format since no capabilities are present
  241. self.assertEqual(b"# v2 git bundle\n", f2.readline())
  242. def test_write_bundle_invalid_version(self) -> None:
  243. """Test writing a bundle with an invalid version."""
  244. bundle = Bundle()
  245. bundle.version = 4 # Invalid version
  246. bundle.capabilities = {}
  247. bundle.prerequisites = []
  248. bundle.references = {}
  249. b = BytesIO()
  250. write_pack_objects(b.write, [])
  251. b.seek(0)
  252. bundle.pack_data = PackData.from_file(b)
  253. f = BytesIO()
  254. with self.assertRaises(AssertionError):
  255. write_bundle(f, bundle)
  256. def test_roundtrip_bundle(self) -> None:
  257. origbundle = Bundle()
  258. origbundle.version = 3
  259. origbundle.capabilities = {"foo": None}
  260. origbundle.references = {b"refs/heads/master": b"ab" * 20}
  261. origbundle.prerequisites = [(b"cc" * 20, "comment")]
  262. b = BytesIO()
  263. write_pack_objects(b.write, [])
  264. b.seek(0)
  265. origbundle.pack_data = PackData.from_file(b)
  266. with tempfile.TemporaryDirectory() as td:
  267. with open(os.path.join(td, "foo"), "wb") as f:
  268. write_bundle(f, origbundle)
  269. with open(os.path.join(td, "foo"), "rb") as f:
  270. newbundle = read_bundle(f)
  271. self.assertEqual(origbundle, newbundle)