issues/test_grouping_mechanism_boundaries.py
Rama main · réplica de lectura del repositorio original.
import ast
import importlib
import sys
from collections import namedtuple
from pathlib import Path
from unittest import TestCase
from django.conf import settings
ROOT = Path(__file__).resolve().parents[1]
GROUPING_MECHANISMS_DIR = ROOT / "issues" / "grouping_mechanisms"
BUILDING_BLOCKS_DIR = GROUPING_MECHANISMS_DIR / "building_blocks"
SENTRY_IMPORT_HASH = "597d25951d00"
VENDORED_SENTRY_DIR = ROOT / "sentry" / f"at_{SENTRY_IMPORT_HASH}"
VENDORED_SENTRY_PACKAGES = (
"sentry.at_597d25951d00",
"sentry.at_glitchtip_af9a700a8706",
)
UNPINNED_SENTRY_PACKAGES = ("sentry.assemble", "sentry.minidump")
BUILDING_BLOCKS_PACKAGE = "issues.grouping_mechanisms.building_blocks"
ImportReference = namedtuple("ImportReference", ["module", "level", "display"])
class GroupingMechanismBoundaryTestCase(TestCase):
def test_python_code_imports_vendored_sentry_through_pinned_root(self):
def skip_import_boundary_file(path):
relative_parts = path.relative_to(ROOT).parts
if relative_parts[0].startswith("."):
return True
try:
path.relative_to(VENDORED_SENTRY_DIR)
return True
except ValueError:
return False
self.assert_imports_allowed(
[
path
for package_dir in [
Path(importlib.import_module(package).__file__).parent
for package in settings.BUGSINK_APPS
]
for path in sorted(package_dir.rglob("*.py"))
if not skip_import_boundary_file(path)
],
self._is_not_unpinned_sentry_import,
)
def test_versioned_mechanisms_import_only_versioned_building_blocks_or_vendored_sentry(self):
self.assert_imports_allowed(
sorted(GROUPING_MECHANISMS_DIR.glob("v[0-9]*.py")),
self._is_mechanism_import,
)
def test_building_block_imports_are_bounded(self):
self.assert_imports_allowed(
sorted(BUILDING_BLOCKS_DIR.glob("v[0-9]*.py")),
self._is_building_block_import,
)
def test_vendored_sentry_package_imports_only_relative_modules_or_generic_python(self):
self.assert_imports_allowed(
sorted((VENDORED_SENTRY_DIR / "grouping").rglob("*.py")),
self._is_relative_or_generic_python_import,
)
def test_vendored_sentry_packages_do_not_cross_import(self):
for path in sorted((ROOT / "sentry").glob("at_*")):
package = "sentry." + path.name
self.assert_imports_allowed(
sorted(path.rglob("*.py")),
lambda module_path, imported: self._is_same_vendored_package_import(package, imported),
)
def _is_mechanism_import(self, path, imported):
if imported.level == 1:
return imported.module is not None and imported.module.startswith("building_blocks.v")
return imported.level == 0 and (
self._is_pinned_sentry_import(imported.module)
or self._is_stdlib(imported.module)
or self._is_django_import(imported.module)
)
def _is_building_block_import(self, path, imported):
def is_lower_building_block_absolute_import(name):
if name is None:
return False
if not name.startswith(BUILDING_BLOCKS_PACKAGE + ".v"):
return False
imported_version = self._version_from_module(name.removeprefix(BUILDING_BLOCKS_PACKAGE + "."))
return imported_version < self._version_from_path(path)
def is_lower_building_block_relative_import(name):
if name is None:
return False
if not name.split(".", 1)[0].startswith("v"):
return False
imported_version = self._version_from_module(name)
return imported_version < self._version_from_path(path)
if imported.level == 1:
return is_lower_building_block_relative_import(imported.module)
return imported.level == 0 and (
self._is_pinned_sentry_import(imported.module)
or is_lower_building_block_absolute_import(imported.module)
or self._is_django_import(imported.module)
or self._is_stdlib(imported.module)
)
def _is_relative_or_generic_python_import(self, path, imported):
return imported.level > 0 or self._is_stdlib(imported.module)
def _is_not_unpinned_sentry_import(self, path, imported):
if imported.module is None:
return True
if imported.module == "sentry" or imported.module.startswith("sentry."):
return (
self._is_pinned_sentry_import(imported.module)
or any(
imported.module == package or imported.module.startswith(package + ".")
for package in UNPINNED_SENTRY_PACKAGES
)
)
return True
def _is_same_vendored_package_import(self, package, imported):
if imported.module is None:
return True
if imported.module == "sentry" or imported.module.startswith("sentry."):
return imported.module == package or imported.module.startswith(package + ".")
if imported.module == "issues.grouping_mechanisms.building_blocks.v1":
return True
if imported.module == "issues" or imported.module.startswith("issues."):
return False
return True
def _is_pinned_sentry_import(self, name):
if name is None:
return False
return any(name == package or name.startswith(package + ".") for package in VENDORED_SENTRY_PACKAGES)
def _is_stdlib(self, name):
if name is None:
return False
root_name = name.split(".", 1)[0]
return root_name in sys.stdlib_module_names
def _is_django_import(self, name):
# Django's small framework utilities are not part of the grouping mechanism behavior we freeze here.
return name == "django" or name.startswith("django.")
def assert_imports_allowed(self, paths, is_allowed):
for path in paths:
for imported in self._imports_in(path):
self.assertTrue(
is_allowed(path, imported),
f"{path.relative_to(ROOT)} imports {imported.display}",
)
def _imports_in(self, path):
def format_from_import(node):
dots = "." * node.level
module = node.module or ""
names = ", ".join(alias.name for alias in node.names)
return f"from {dots}{module} import {names}"
tree = ast.parse(path.read_text(), filename=str(path))
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for alias in node.names:
yield ImportReference(alias.name, 0, alias.name)
if isinstance(node, ast.ImportFrom):
yield ImportReference(node.module, node.level, format_from_import(node))
def _version_from_path(self, path):
return int(path.stem.removeprefix("v"))
def _version_from_module(self, name):
version = name.split(".", 1)[0]
if not version.startswith("v"):
return 0
return int(version.removeprefix("v"))