import stat
import unittest
import yaml
from collections import namedtuple
from unittest.mock import patch
from parameterized import parameterized
import __res as res
NAMESPACE_PREFIX = b"py/namespace/"
TEST_SOURCE_ROOT = "/home/arcadia"
class ImporterMocks:
def __init__(self, mock_fs, mock_resources):
self._mock_fs = mock_fs
self._mock_resources = mock_resources
self._patchers = [
patch("__res.iter_keys", wraps=self._iter_keys),
patch("__res.__resource.find", wraps=self._resource_find),
patch("__res._path_isfile", wraps=self._path_isfile),
patch("__res._os.listdir", wraps=self._os_listdir),
patch("__res._os.lstat", wraps=self._os_lstat),
]
for patcher in self._patchers:
patcher.start()
def stop(self):
for patcher in self._patchers:
patcher.stop()
def _iter_keys(self, prefix):
assert prefix == NAMESPACE_PREFIX
for k in self._mock_resources.keys():
yield k, k.removeprefix(prefix)
def _resource_find(self, key):
return self._mock_resources.get(key)
def _lookup_mock_fs(self, filename):
path = filename.lstrip("/").split("/")
curdir = self._mock_fs
for item in path:
if item in curdir:
curdir = curdir[item]
else:
return None
return curdir
def _path_isfile(self, filename):
f = self._lookup_mock_fs(filename)
return isinstance(f, str)
def _os_lstat(self, filename):
f = self._lookup_mock_fs(filename)
mode = stat.S_IFDIR if isinstance(f, dict) else stat.S_IFREG
return namedtuple("fake_stat_type", "st_mode")(st_mode=mode)
def _os_listdir(self, dirname):
f = self._lookup_mock_fs(dirname)
if isinstance(f, dict):
return f.keys()
else:
return []
class ArcadiaSourceFinderTestCase(unittest.TestCase):
def setUp(self):
self.import_mock = ImporterMocks(yaml.safe_load(self._get_mock_fs()), self._get_mock_resources())
self.arcadia_source_finder = res.ArcadiaSourceFinder(TEST_SOURCE_ROOT)
def tearDown(self):
self.import_mock.stop()
def _get_mock_fs(self):
raise NotImplementedError()
def _get_mock_resources(self):
raise NotImplementedError()
class TestLibraryWithoutNamespace(ArcadiaSourceFinderTestCase):
def _get_mock_fs(self):
return """
home:
arcadia:
project:
lib:
mod1.py: ""
package1:
mod2.py: ""
"""
def _get_mock_resources(self):
return {
b"py/namespace/unique_prefix1/project/lib": b"project.lib.",
}
@parameterized.expand(
[
("project.lib.mod1", b"project/lib/mod1.py"),
("project.lib.package1.mod2", b"project/lib/package1/mod2.py"),
("project.lib.unknown_module", None),
("project.lib", None), # package
]
)
def test_get_module_path(self, module, path):
assert path == self.arcadia_source_finder.get_module_path(module)
@parameterized.expand(
[
("project.lib.mod1", False),
("project.lib.package1.mod2", False),
("project", True),
("project.lib", True),
("project.lib.package1", True),
]
)
def test_is_packages(self, module, is_package):
assert is_package == self.arcadia_source_finder.is_package(module)
def test_is_package_for_unknown_module(self):
self.assertRaises(
ImportError,
lambda: self.arcadia_source_finder.is_package("project.lib.package2"),
)
@parameterized.expand(
[
(
"",
{
("PFX.project", True),
},
),
(
"project.",
{
("PFX.lib", True),
},
),
(
"project.lib.",
{
("PFX.mod1", False),
("PFX.package1", True),
},
),
(
"project.lib.package1.",
{
("PFX.mod2", False),
},
),
]
)
def test_iter_modules(self, package_prefix, expected):
got = self.arcadia_source_finder.iter_modules(package_prefix, "PFX.")
assert expected == set(got)
# Check iter_modules() don't crash and return correct result after not existing module was requested
def test_iter_modules_after_unknown_module_import(self):
self.arcadia_source_finder.get_module_path("project.unknown_module")
assert {("lib", True)} == set(self.arcadia_source_finder.iter_modules("project.", ""))
class TestLibraryExtendedFromAnotherLibrary(ArcadiaSourceFinderTestCase):
def _get_mock_fs(self):
return """
home:
arcadia:
project:
lib:
mod1.py: ''
lib_extension:
mod2.py: ''
"""
def _get_mock_resources(self):
return {
b"py/namespace/unique_prefix1/project/lib": b"project.lib.",
b"py/namespace/unique_prefix2/project/lib_extension": b"project.lib.",
}
@parameterized.expand(
[
("project.lib.mod1", b"project/lib/mod1.py"),
("project.lib.mod2", b"project/lib_extension/mod2.py"),
]
)
def test_get_module_path(self, module, path):
assert path == self.arcadia_source_finder.get_module_path(module)
@parameterized.expand(
[
(
"project.lib.",
{
("PFX.mod1", False),
("PFX.mod2", False),
},
),
]
)
def test_iter_modules(self, package_prefix, expected):
got = self.arcadia_source_finder.iter_modules(package_prefix, "PFX.")
assert expected == set(got)
class TestNamespaceAndTopLevelLibraries(ArcadiaSourceFinderTestCase):
def _get_mock_fs(self):
return """
home:
arcadia:
project:
ns_lib:
mod1.py: ''
top_level_lib:
mod2.py: ''
"""
def _get_mock_resources(self):
return {
b"py/namespace/unique_prefix1/project/ns_lib": b"ns.",
b"py/namespace/unique_prefix2/project/top_level_lib": b".",
}
@parameterized.expand(
[
("ns.mod1", b"project/ns_lib/mod1.py"),
("mod2", b"project/top_level_lib/mod2.py"),
]
)
def test_get_module_path(self, module, path):
assert path == self.arcadia_source_finder.get_module_path(module)
@parameterized.expand(
[
("ns", True),
("ns.mod1", False),
("mod2", False),
]
)
def test_is_packages(self, module, is_package):
assert is_package == self.arcadia_source_finder.is_package(module)
@parameterized.expand(
[
"project",
"project.ns_lib",
"project.top_level_lib",
]
)
def test_is_package_for_unknown_modules(self, module):
self.assertRaises(ImportError, lambda: self.arcadia_source_finder.is_package(module))
@parameterized.expand(
[
(
"",
{
("PFX.ns", True),
("PFX.mod2", False),
},
),
(
"ns.",
{
("PFX.mod1", False),
},
),
]
)
def test_iter_modules(self, package_prefix, expected):
got = self.arcadia_source_finder.iter_modules(package_prefix, "PFX.")
assert expected == set(got)
class TestIgnoreDirectoriesWithYaMakeFile(ArcadiaSourceFinderTestCase):
"""Packages and modules from tests should not be part of pylib namespace"""
def _get_mock_fs(self):
return """
home:
arcadia:
contrib:
python:
pylib:
mod1.py: ""
tests:
conftest.py: ""
ya.make: ""
"""
def _get_mock_resources(self):
return {
b"py/namespace/unique_prefix1/contrib/python/pylib": b"pylib.",
}
def test_get_module_path_for_lib(self):
assert b"contrib/python/pylib/mod1.py" == self.arcadia_source_finder.get_module_path("pylib.mod1")
def test_get_module_for_tests(self):
assert self.arcadia_source_finder.get_module_path("pylib.tests.conftest") is None
def test_is_package_for_tests(self):
self.assertRaises(ImportError, lambda: self.arcadia_source_finder.is_package("pylib.tests"))
class TestMergingNamespaceAndDirectoryPackages(ArcadiaSourceFinderTestCase):
"""Merge parent package (top level in this test) dirs with namespace dirs (DEVTOOLS-8979)"""
def _get_mock_fs(self):
return """
home:
arcadia:
contrib:
python:
pylint:
ya.make: ""
pylint:
__init__.py: ""
patcher:
patch.py: ""
ya.make: ""
"""
def _get_mock_resources(self):
return {
b"py/namespace/unique_prefix1/contrib/python/pylint": b".",
b"py/namespace/unique_prefix1/contrib/python/pylint/patcher": b"pylint.",
}
@parameterized.expand(
[
("pylint.__init__", b"contrib/python/pylint/pylint/__init__.py"),
("pylint.patch", b"contrib/python/pylint/patcher/patch.py"),
]
)
def test_get_module_path(self, module, path):
assert path == self.arcadia_source_finder.get_module_path(module)
class TestEmptyResources(ArcadiaSourceFinderTestCase):
def _get_mock_fs(self):
return """
home:
arcadia:
project:
lib:
mod1.py: ''
"""
def _get_mock_resources(self):
return {}
def test_get_module_path(self):
assert self.arcadia_source_finder.get_module_path("project.lib.mod1") is None
def test_is_package(self):
self.assertRaises(ImportError, lambda: self.arcadia_source_finder.is_package("project"))
def test_iter_modules(self):
assert [] == list(self.arcadia_source_finder.iter_modules("", "PFX."))
class TestDictionaryChangedSizeDuringIteration(ArcadiaSourceFinderTestCase):
def _get_mock_fs(self):
return """
home:
arcadia:
project:
lib1:
mod1.py: ''
lib2:
mod2.py: ''
"""
def _get_mock_resources(self):
return {
b"py/namespace/unique_prefix1/project/lib1": b"project.lib1.",
b"py/namespace/unique_prefix1/project/lib2": b"project.lib2.",
}
def test_no_crash_on_recursive_iter_modules(self):
for package in self.arcadia_source_finder.iter_modules("project.", ""):
for _ in self.arcadia_source_finder.iter_modules(package[0], ""):
pass