test_arcadia_source_finder.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. import stat
  2. import unittest
  3. import yaml
  4. from collections import namedtuple
  5. from unittest.mock import patch
  6. from parameterized import parameterized
  7. import __res as res
  8. NAMESPACE_PREFIX = b"py/namespace/"
  9. TEST_SOURCE_ROOT = "/home/arcadia"
  10. class ImporterMocks:
  11. def __init__(self, mock_fs, mock_resources):
  12. self._mock_fs = mock_fs
  13. self._mock_resources = mock_resources
  14. self._patchers = [
  15. patch("__res.iter_keys", wraps=self._iter_keys),
  16. patch("__res.__resource.find", wraps=self._resource_find),
  17. patch("__res._path_isfile", wraps=self._path_isfile),
  18. patch("__res._os.listdir", wraps=self._os_listdir),
  19. patch("__res._os.lstat", wraps=self._os_lstat),
  20. ]
  21. for patcher in self._patchers:
  22. patcher.start()
  23. def stop(self):
  24. for patcher in self._patchers:
  25. patcher.stop()
  26. def _iter_keys(self, prefix):
  27. assert prefix == NAMESPACE_PREFIX
  28. for k in self._mock_resources.keys():
  29. yield k, k.removeprefix(prefix)
  30. def _resource_find(self, key):
  31. return self._mock_resources.get(key)
  32. def _lookup_mock_fs(self, filename):
  33. path = filename.lstrip("/").split("/")
  34. curdir = self._mock_fs
  35. for item in path:
  36. if item in curdir:
  37. curdir = curdir[item]
  38. else:
  39. return None
  40. return curdir
  41. def _path_isfile(self, filename):
  42. f = self._lookup_mock_fs(filename)
  43. return isinstance(f, str)
  44. def _os_lstat(self, filename):
  45. f = self._lookup_mock_fs(filename)
  46. mode = stat.S_IFDIR if isinstance(f, dict) else stat.S_IFREG
  47. return namedtuple("fake_stat_type", "st_mode")(st_mode=mode)
  48. def _os_listdir(self, dirname):
  49. f = self._lookup_mock_fs(dirname)
  50. if isinstance(f, dict):
  51. return f.keys()
  52. else:
  53. return []
  54. class ArcadiaSourceFinderTestCase(unittest.TestCase):
  55. def setUp(self):
  56. self.import_mock = ImporterMocks(yaml.safe_load(self._get_mock_fs()), self._get_mock_resources())
  57. self.arcadia_source_finder = res.ArcadiaSourceFinder(TEST_SOURCE_ROOT)
  58. def tearDown(self):
  59. self.import_mock.stop()
  60. def _get_mock_fs(self):
  61. raise NotImplementedError()
  62. def _get_mock_resources(self):
  63. raise NotImplementedError()
  64. class TestLibraryWithoutNamespace(ArcadiaSourceFinderTestCase):
  65. def _get_mock_fs(self):
  66. return """
  67. home:
  68. arcadia:
  69. project:
  70. lib:
  71. mod1.py: ""
  72. package1:
  73. mod2.py: ""
  74. """
  75. def _get_mock_resources(self):
  76. return {
  77. b"py/namespace/unique_prefix1/project/lib": b"project.lib.",
  78. }
  79. @parameterized.expand(
  80. [
  81. ("project.lib.mod1", b"project/lib/mod1.py"),
  82. ("project.lib.package1.mod2", b"project/lib/package1/mod2.py"),
  83. ("project.lib.unknown_module", None),
  84. ("project.lib", None), # package
  85. ]
  86. )
  87. def test_get_module_path(self, module, path):
  88. assert path == self.arcadia_source_finder.get_module_path(module)
  89. @parameterized.expand(
  90. [
  91. ("project.lib.mod1", False),
  92. ("project.lib.package1.mod2", False),
  93. ("project", True),
  94. ("project.lib", True),
  95. ("project.lib.package1", True),
  96. ]
  97. )
  98. def test_is_packages(self, module, is_package):
  99. assert is_package == self.arcadia_source_finder.is_package(module)
  100. def test_is_package_for_unknown_module(self):
  101. self.assertRaises(
  102. ImportError,
  103. lambda: self.arcadia_source_finder.is_package("project.lib.package2"),
  104. )
  105. @parameterized.expand(
  106. [
  107. (
  108. "",
  109. {
  110. ("PFX.project", True),
  111. },
  112. ),
  113. (
  114. "project.",
  115. {
  116. ("PFX.lib", True),
  117. },
  118. ),
  119. (
  120. "project.lib.",
  121. {
  122. ("PFX.mod1", False),
  123. ("PFX.package1", True),
  124. },
  125. ),
  126. (
  127. "project.lib.package1.",
  128. {
  129. ("PFX.mod2", False),
  130. },
  131. ),
  132. ]
  133. )
  134. def test_iter_modules(self, package_prefix, expected):
  135. got = self.arcadia_source_finder.iter_modules(package_prefix, "PFX.")
  136. assert expected == set(got)
  137. # Check iter_modules() don't crash and return correct result after not existing module was requested
  138. def test_iter_modules_after_unknown_module_import(self):
  139. self.arcadia_source_finder.get_module_path("project.unknown_module")
  140. assert {("lib", True)} == set(self.arcadia_source_finder.iter_modules("project.", ""))
  141. class TestLibraryExtendedFromAnotherLibrary(ArcadiaSourceFinderTestCase):
  142. def _get_mock_fs(self):
  143. return """
  144. home:
  145. arcadia:
  146. project:
  147. lib:
  148. mod1.py: ''
  149. lib_extension:
  150. mod2.py: ''
  151. """
  152. def _get_mock_resources(self):
  153. return {
  154. b"py/namespace/unique_prefix1/project/lib": b"project.lib.",
  155. b"py/namespace/unique_prefix2/project/lib_extension": b"project.lib.",
  156. }
  157. @parameterized.expand(
  158. [
  159. ("project.lib.mod1", b"project/lib/mod1.py"),
  160. ("project.lib.mod2", b"project/lib_extension/mod2.py"),
  161. ]
  162. )
  163. def test_get_module_path(self, module, path):
  164. assert path == self.arcadia_source_finder.get_module_path(module)
  165. @parameterized.expand(
  166. [
  167. (
  168. "project.lib.",
  169. {
  170. ("PFX.mod1", False),
  171. ("PFX.mod2", False),
  172. },
  173. ),
  174. ]
  175. )
  176. def test_iter_modules(self, package_prefix, expected):
  177. got = self.arcadia_source_finder.iter_modules(package_prefix, "PFX.")
  178. assert expected == set(got)
  179. class TestNamespaceAndTopLevelLibraries(ArcadiaSourceFinderTestCase):
  180. def _get_mock_fs(self):
  181. return """
  182. home:
  183. arcadia:
  184. project:
  185. ns_lib:
  186. mod1.py: ''
  187. top_level_lib:
  188. mod2.py: ''
  189. """
  190. def _get_mock_resources(self):
  191. return {
  192. b"py/namespace/unique_prefix1/project/ns_lib": b"ns.",
  193. b"py/namespace/unique_prefix2/project/top_level_lib": b".",
  194. }
  195. @parameterized.expand(
  196. [
  197. ("ns.mod1", b"project/ns_lib/mod1.py"),
  198. ("mod2", b"project/top_level_lib/mod2.py"),
  199. ]
  200. )
  201. def test_get_module_path(self, module, path):
  202. assert path == self.arcadia_source_finder.get_module_path(module)
  203. @parameterized.expand(
  204. [
  205. ("ns", True),
  206. ("ns.mod1", False),
  207. ("mod2", False),
  208. ]
  209. )
  210. def test_is_packages(self, module, is_package):
  211. assert is_package == self.arcadia_source_finder.is_package(module)
  212. @parameterized.expand(
  213. [
  214. "project",
  215. "project.ns_lib",
  216. "project.top_level_lib",
  217. ]
  218. )
  219. def test_is_package_for_unknown_modules(self, module):
  220. self.assertRaises(ImportError, lambda: self.arcadia_source_finder.is_package(module))
  221. @parameterized.expand(
  222. [
  223. (
  224. "",
  225. {
  226. ("PFX.ns", True),
  227. ("PFX.mod2", False),
  228. },
  229. ),
  230. (
  231. "ns.",
  232. {
  233. ("PFX.mod1", False),
  234. },
  235. ),
  236. ]
  237. )
  238. def test_iter_modules(self, package_prefix, expected):
  239. got = self.arcadia_source_finder.iter_modules(package_prefix, "PFX.")
  240. assert expected == set(got)
  241. class TestIgnoreDirectoriesWithYaMakeFile(ArcadiaSourceFinderTestCase):
  242. """Packages and modules from tests should not be part of pylib namespace"""
  243. def _get_mock_fs(self):
  244. return """
  245. home:
  246. arcadia:
  247. contrib:
  248. python:
  249. pylib:
  250. mod1.py: ""
  251. tests:
  252. conftest.py: ""
  253. ya.make: ""
  254. """
  255. def _get_mock_resources(self):
  256. return {
  257. b"py/namespace/unique_prefix1/contrib/python/pylib": b"pylib.",
  258. }
  259. def test_get_module_path_for_lib(self):
  260. assert b"contrib/python/pylib/mod1.py" == self.arcadia_source_finder.get_module_path("pylib.mod1")
  261. def test_get_module_for_tests(self):
  262. assert self.arcadia_source_finder.get_module_path("pylib.tests.conftest") is None
  263. def test_is_package_for_tests(self):
  264. self.assertRaises(ImportError, lambda: self.arcadia_source_finder.is_package("pylib.tests"))
  265. class TestMergingNamespaceAndDirectoryPackages(ArcadiaSourceFinderTestCase):
  266. """Merge parent package (top level in this test) dirs with namespace dirs (DEVTOOLS-8979)"""
  267. def _get_mock_fs(self):
  268. return """
  269. home:
  270. arcadia:
  271. contrib:
  272. python:
  273. pylint:
  274. ya.make: ""
  275. pylint:
  276. __init__.py: ""
  277. patcher:
  278. patch.py: ""
  279. ya.make: ""
  280. """
  281. def _get_mock_resources(self):
  282. return {
  283. b"py/namespace/unique_prefix1/contrib/python/pylint": b".",
  284. b"py/namespace/unique_prefix1/contrib/python/pylint/patcher": b"pylint.",
  285. }
  286. @parameterized.expand(
  287. [
  288. ("pylint.__init__", b"contrib/python/pylint/pylint/__init__.py"),
  289. ("pylint.patch", b"contrib/python/pylint/patcher/patch.py"),
  290. ]
  291. )
  292. def test_get_module_path(self, module, path):
  293. assert path == self.arcadia_source_finder.get_module_path(module)
  294. class TestEmptyResources(ArcadiaSourceFinderTestCase):
  295. def _get_mock_fs(self):
  296. return """
  297. home:
  298. arcadia:
  299. project:
  300. lib:
  301. mod1.py: ''
  302. """
  303. def _get_mock_resources(self):
  304. return {}
  305. def test_get_module_path(self):
  306. assert self.arcadia_source_finder.get_module_path("project.lib.mod1") is None
  307. def test_is_package(self):
  308. self.assertRaises(ImportError, lambda: self.arcadia_source_finder.is_package("project"))
  309. def test_iter_modules(self):
  310. assert [] == list(self.arcadia_source_finder.iter_modules("", "PFX."))
  311. class TestDictionaryChangedSizeDuringIteration(ArcadiaSourceFinderTestCase):
  312. def _get_mock_fs(self):
  313. return """
  314. home:
  315. arcadia:
  316. project:
  317. lib1:
  318. mod1.py: ''
  319. lib2:
  320. mod2.py: ''
  321. """
  322. def _get_mock_resources(self):
  323. return {
  324. b"py/namespace/unique_prefix1/project/lib1": b"project.lib1.",
  325. b"py/namespace/unique_prefix1/project/lib2": b"project.lib2.",
  326. }
  327. def test_no_crash_on_recursive_iter_modules(self):
  328. for package in self.arcadia_source_finder.iter_modules("project.", ""):
  329. for _ in self.arcadia_source_finder.iter_modules(package[0], ""):
  330. pass