__init__.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. # __init__.py -- The tests for dulwich
  2. # Copyright (C) 2007 James Westby <jw+debian@jameswestby.net>
  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 Dulwich."""
  22. __all__ = [
  23. "BlackboxTestCase",
  24. "SkipTest",
  25. "TestCase",
  26. "expectedFailure",
  27. "skipIf",
  28. ]
  29. import doctest
  30. import os
  31. import shutil
  32. import subprocess
  33. import sys
  34. import sysconfig
  35. import tempfile
  36. # If Python itself provides an exception, use that
  37. import unittest
  38. from collections.abc import Sequence
  39. from typing import ClassVar
  40. from unittest import SkipTest, expectedFailure, skipIf
  41. from unittest import TestCase as _TestCase
  42. class DependencyMissing(SkipTest):
  43. def __init__(self, dependency: str) -> None:
  44. super().__init__(f"Dependency {dependency} missing")
  45. class TestCase(_TestCase):
  46. def setUp(self) -> None:
  47. super().setUp()
  48. self.overrideEnv("HOME", "/nonexistent")
  49. self.overrideEnv("GIT_CONFIG_NOSYSTEM", "1")
  50. def overrideEnv(self, name: str, value: str | None) -> None:
  51. def restore() -> None:
  52. if oldval is not None:
  53. os.environ[name] = oldval
  54. elif name in os.environ:
  55. del os.environ[name]
  56. oldval = os.environ.get(name)
  57. if value is not None:
  58. os.environ[name] = value
  59. elif name in os.environ:
  60. del os.environ[name]
  61. self.addCleanup(restore)
  62. class BlackboxTestCase(TestCase):
  63. """Blackbox testing."""
  64. # TODO(jelmer): Include more possible binary paths.
  65. bin_directories: ClassVar[list[str]] = [
  66. os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "bin")),
  67. sysconfig.get_path("scripts"),
  68. "/usr/bin",
  69. "/usr/local/bin",
  70. ]
  71. def bin_path(self, name: str) -> str:
  72. """Determine the full path of a binary.
  73. Args:
  74. name: Name of the script
  75. Returns: Full path
  76. """
  77. for d in self.bin_directories:
  78. p = os.path.join(d, name)
  79. if os.path.isfile(p):
  80. return p
  81. else:
  82. raise SkipTest(f"Unable to find binary {name}")
  83. def run_command(self, name: str, args: Sequence[str]) -> subprocess.Popen[bytes]:
  84. """Run a Dulwich command.
  85. Args:
  86. name: Name of the command, as it exists in bin/
  87. args: Arguments to the command
  88. """
  89. env = dict(os.environ)
  90. env["PYTHONPATH"] = os.pathsep.join(sys.path)
  91. # Since they don't have any extensions, Windows can't recognize
  92. # executablility of the Python files in /bin. Even then, we'd have to
  93. # expect the user to set up file associations for .py files.
  94. #
  95. # Save us from all that headache and call python with the bin script.
  96. argv = [sys.executable, self.bin_path(name), *args]
  97. return subprocess.Popen(
  98. argv,
  99. stdout=subprocess.PIPE,
  100. stdin=subprocess.PIPE,
  101. stderr=subprocess.PIPE,
  102. env=env,
  103. )
  104. def self_test_suite() -> unittest.TestSuite:
  105. names = [
  106. "annotate",
  107. "approxidate",
  108. "archive",
  109. "attrs",
  110. "bisect",
  111. "bitmap",
  112. "blackbox",
  113. "bundle",
  114. "cli",
  115. "cli_cherry_pick",
  116. "cli_merge",
  117. "client",
  118. "cloud_gcs",
  119. "commit_graph",
  120. "config",
  121. "credentials",
  122. "diff",
  123. "diff_tree",
  124. "dumb",
  125. "fastexport",
  126. "file",
  127. "filter_branch",
  128. "filters",
  129. "gc",
  130. "grafts",
  131. "graph",
  132. "greenthreads",
  133. "hooks",
  134. "ignore",
  135. "index",
  136. "lfs",
  137. "lfs_integration",
  138. "line_ending",
  139. "log_utils",
  140. "lru_cache",
  141. "mailmap",
  142. "mbox",
  143. "merge",
  144. "merge_drivers",
  145. "missing_obj_finder",
  146. "notes",
  147. "objects",
  148. "objectspec",
  149. "object_store",
  150. "pack",
  151. "patch",
  152. "porcelain",
  153. "porcelain_cherry_pick",
  154. "porcelain_filters",
  155. "porcelain_lfs",
  156. "porcelain_merge",
  157. "porcelain_notes",
  158. "protocol",
  159. "rebase",
  160. "reflog",
  161. "refs",
  162. "reftable",
  163. "repository",
  164. "server",
  165. "sparse_patterns",
  166. "stash",
  167. "stripspace",
  168. "submodule",
  169. "trailers",
  170. "utils",
  171. "walk",
  172. "web",
  173. "whitespace",
  174. "worktree",
  175. ]
  176. module_names = ["tests.test_" + name for name in names]
  177. loader = unittest.TestLoader()
  178. return loader.loadTestsFromNames(module_names)
  179. def tutorial_test_suite() -> unittest.TestSuite:
  180. tutorial = [
  181. "introduction",
  182. "file-format",
  183. "repo",
  184. "object-store",
  185. "remote",
  186. "conclusion",
  187. ]
  188. tutorial_files = [f"../docs/tutorial/{name}.txt" for name in tutorial]
  189. to_restore = []
  190. def overrideEnv(name: str, value: str | None) -> None:
  191. oldval = os.environ.get(name)
  192. if value is not None:
  193. os.environ[name] = value
  194. else:
  195. del os.environ[name]
  196. to_restore.append((name, oldval))
  197. def setup(test: doctest.DocTest) -> None:
  198. test.__old_cwd = os.getcwd() # type: ignore[attr-defined]
  199. test.tempdir = tempfile.mkdtemp() # type: ignore[attr-defined]
  200. test.globs.update({"tempdir": test.tempdir}) # type: ignore[attr-defined]
  201. os.chdir(test.tempdir) # type: ignore[attr-defined]
  202. overrideEnv("HOME", "/nonexistent")
  203. overrideEnv("GIT_CONFIG_NOSYSTEM", "1")
  204. def teardown(test: doctest.DocTest) -> None:
  205. os.chdir(test.__old_cwd) # type: ignore[attr-defined]
  206. shutil.rmtree(test.tempdir) # type: ignore[attr-defined]
  207. for name, oldval in to_restore:
  208. if oldval is not None:
  209. os.environ[name] = oldval
  210. else:
  211. del os.environ[name]
  212. to_restore.clear()
  213. return doctest.DocFileSuite(
  214. module_relative=True,
  215. package="tests",
  216. setUp=setup,
  217. tearDown=teardown,
  218. *tutorial_files,
  219. )
  220. def nocompat_test_suite() -> unittest.TestSuite:
  221. result = unittest.TestSuite()
  222. result.addTests(self_test_suite())
  223. result.addTests(tutorial_test_suite())
  224. from .contrib import test_suite as contrib_test_suite
  225. result.addTests(contrib_test_suite())
  226. return result
  227. def compat_test_suite() -> unittest.TestSuite:
  228. result = unittest.TestSuite()
  229. from .compat import test_suite as compat_test_suite
  230. result.addTests(compat_test_suite())
  231. return result
  232. def test_suite() -> unittest.TestSuite:
  233. result = unittest.TestSuite()
  234. result.addTests(self_test_suite())
  235. if sys.platform != "win32":
  236. result.addTests(tutorial_test_suite())
  237. from .compat import test_suite as compat_test_suite
  238. result.addTests(compat_test_suite())
  239. from .contrib import test_suite as contrib_test_suite
  240. result.addTests(contrib_test_suite())
  241. return result