test_server.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. # test_server.py -- Compatibility tests for git server.
  2. # Copyright (C) 2010 Google, Inc.
  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. """Compatibility tests between Dulwich and the cgit server.
  22. Warning: these tests should be fairly stable, but when writing/debugging new
  23. tests, deadlocks may freeze the test process such that it cannot be
  24. Ctrl-C'ed. On POSIX systems, you can kill the tests with Ctrl-Z, "kill %".
  25. """
  26. import os
  27. import shutil
  28. import sys
  29. import tempfile
  30. import threading
  31. from dulwich.object_format import SHA256
  32. from dulwich.objects import Blob, Commit, Tree
  33. from dulwich.repo import Repo
  34. from dulwich.server import DictBackend, TCPGitServer
  35. from .. import skipIf
  36. from .server_utils import NoSideBand64kReceivePackHandler, ServerTests
  37. from .utils import CompatTestCase, require_git_version, run_git_or_fail
  38. @skipIf(sys.platform == "win32", "Broken on windows, with very long fail time.")
  39. class GitServerTestCase(ServerTests, CompatTestCase):
  40. """Tests for client/server compatibility.
  41. This server test case does not use side-band-64k in git-receive-pack.
  42. """
  43. protocol = "git"
  44. def _handlers(self):
  45. return {b"git-receive-pack": NoSideBand64kReceivePackHandler}
  46. def _check_server(self, dul_server) -> None:
  47. receive_pack_handler_cls = dul_server.handlers[b"git-receive-pack"]
  48. caps = receive_pack_handler_cls.capabilities()
  49. self.assertNotIn(b"side-band-64k", caps)
  50. def _start_server(self, repo):
  51. backend = DictBackend({b"/": repo})
  52. dul_server = TCPGitServer(backend, b"localhost", 0, handlers=self._handlers())
  53. self._check_server(dul_server)
  54. # Start server in a thread
  55. server_thread = threading.Thread(target=dul_server.serve)
  56. server_thread.daemon = True # Make thread daemon so it dies with main thread
  57. server_thread.start()
  58. # Add cleanup in the correct order
  59. def cleanup_server():
  60. dul_server.shutdown()
  61. dul_server.server_close()
  62. # Give thread a moment to exit cleanly
  63. server_thread.join(timeout=1.0)
  64. self.addCleanup(cleanup_server)
  65. self._server = dul_server
  66. _, port = self._server.socket.getsockname()
  67. return port
  68. @skipIf(sys.platform == "win32", "Broken on windows, with very long fail time.")
  69. class GitServerSideBand64kTestCase(GitServerTestCase):
  70. """Tests for client/server compatibility with side-band-64k support."""
  71. # side-band-64k in git-receive-pack was introduced in git 1.7.0.2
  72. min_git_version = (1, 7, 0, 2)
  73. def setUp(self) -> None:
  74. super().setUp()
  75. # side-band-64k is broken in the windows client.
  76. # https://github.com/msysgit/git/issues/101
  77. # Fix has landed for the 1.9.3 release.
  78. if os.name == "nt":
  79. require_git_version((1, 9, 3))
  80. def _handlers(self) -> None:
  81. return None # default handlers include side-band-64k
  82. def _check_server(self, server) -> None:
  83. receive_pack_handler_cls = server.handlers[b"git-receive-pack"]
  84. caps = receive_pack_handler_cls.capabilities()
  85. self.assertIn(b"side-band-64k", caps)
  86. class GitServerSHA256TestCase(CompatTestCase):
  87. """Tests for SHA-256 repository server compatibility with git client."""
  88. protocol = "git"
  89. # SHA-256 support was introduced in git 2.29.0
  90. min_git_version = (2, 29, 0)
  91. def setUp(self) -> None:
  92. super().setUp()
  93. require_git_version(self.min_git_version)
  94. def _start_server(self, repo):
  95. backend = DictBackend({b"/": repo})
  96. dul_server = TCPGitServer(backend, b"localhost", 0)
  97. # Start server in a thread
  98. server_thread = threading.Thread(target=dul_server.serve)
  99. server_thread.daemon = True
  100. server_thread.start()
  101. # Add cleanup
  102. def cleanup_server():
  103. dul_server.shutdown()
  104. dul_server.server_close()
  105. server_thread.join(timeout=1.0)
  106. self.addCleanup(cleanup_server)
  107. self._server = dul_server
  108. _, port = self._server.socket.getsockname()
  109. return port
  110. def url(self, port) -> str:
  111. return f"{self.protocol}://localhost:{port}/"
  112. def test_clone_sha256_repo_from_dulwich_server(self) -> None:
  113. """Test that git client can clone SHA-256 repo from dulwich server."""
  114. # Create SHA-256 repository with dulwich
  115. repo_path = tempfile.mkdtemp()
  116. self.addCleanup(shutil.rmtree, repo_path)
  117. source_repo = Repo.init(repo_path, mkdir=False, object_format="sha256")
  118. # Create test content
  119. blob = Blob.from_string(b"Test SHA-256 content from dulwich server")
  120. tree = Tree()
  121. tree.add(b"test.txt", 0o100644, blob.get_id(SHA256))
  122. commit = Commit()
  123. commit.tree = tree.get_id(SHA256)
  124. commit.author = commit.committer = b"Test User <test@example.com>"
  125. commit.commit_time = commit.author_time = 1234567890
  126. commit.commit_timezone = commit.author_timezone = 0
  127. commit.message = b"Test SHA-256 commit"
  128. # Add objects to repo
  129. source_repo.object_store.add_object(blob)
  130. source_repo.object_store.add_object(tree)
  131. source_repo.object_store.add_object(commit)
  132. # Set master ref
  133. source_repo.refs[b"refs/heads/master"] = commit.get_id(SHA256)
  134. # Start dulwich server
  135. port = self._start_server(source_repo)
  136. # Clone with git client
  137. clone_path = tempfile.mkdtemp()
  138. self.addCleanup(shutil.rmtree, clone_path)
  139. clone_dir = os.path.join(clone_path, "cloned_repo")
  140. run_git_or_fail(["clone", self.url(port), clone_dir], cwd=clone_path)
  141. # Verify cloned repo is SHA-256
  142. cloned_repo = Repo(clone_dir)
  143. self.addCleanup(cloned_repo.close)
  144. self.assertEqual(cloned_repo.object_format, SHA256)
  145. # Verify object format config
  146. output = run_git_or_fail(
  147. ["config", "--get", "extensions.objectformat"], cwd=clone_dir
  148. )
  149. self.assertEqual(output.strip(), b"sha256")
  150. # Verify commit was cloned
  151. cloned_head = cloned_repo.refs[b"refs/heads/master"]
  152. self.assertEqual(len(cloned_head), 64) # SHA-256 length
  153. self.assertEqual(cloned_head, commit.get_id(SHA256))
  154. # Verify git can read the commit
  155. log_output = run_git_or_fail(["log", "--format=%s", "-n", "1"], cwd=clone_dir)
  156. self.assertEqual(log_output.strip(), b"Test SHA-256 commit")
  157. source_repo.close()