-
Notifications
You must be signed in to change notification settings - Fork 605
Expand file tree
/
Copy pathtest_shadowed_module.py
More file actions
136 lines (111 loc) · 4.71 KB
/
test_shadowed_module.py
File metadata and controls
136 lines (111 loc) · 4.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import sys
import ast
import types
import pkgutil
import importlib
import pathlib
import pytest
from sentry_sdk import integrations
from sentry_sdk.integrations import _DEFAULT_INTEGRATIONS, Integration
def pytest_generate_tests(metafunc):
"""
All submodules of sentry_sdk.integrations are picked up, so modules
without a subclass of sentry_sdk.integrations.Integration are also tested
for poorly gated imports.
This approach was chosen to keep the implementation simple.
"""
if "integration_submodule_name" in metafunc.fixturenames:
submodule_names = {
submodule_name
for _, submodule_name, _ in pkgutil.walk_packages(integrations.__path__)
} - {"beam", "spark", "unraisablehook"}
metafunc.parametrize(
"integration_submodule_name",
submodule_names,
)
def find_unrecognized_dependencies(tree):
"""
Finds unrecognized imports in the AST for a Python module. In an empty
environment the set of non-standard library modules is returned.
"""
unrecognized_dependencies = set()
package_name = lambda name: name.split(".")[0]
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for alias in node.names:
root = package_name(alias.name)
try:
if not importlib.util.find_spec(root):
unrecognized_dependencies.add(root)
except ValueError:
continue
elif isinstance(node, ast.ImportFrom):
# if node.level is not 0 the import is relative
if node.level > 0 or node.module is None:
continue
root = package_name(node.module)
try:
if not importlib.util.find_spec(root):
unrecognized_dependencies.add(root)
except ValueError:
continue
return unrecognized_dependencies
@pytest.mark.skipif(
sys.version_info < (3, 7), reason="asyncpg imports __future__.annotations"
)
def test_shadowed_modules_when_importing_integrations(
sentry_init, integration_submodule_name
):
"""
Check that importing integrations for third-party module raises an
DidNotEnable exception when the associated module is shadowed by an empty
module.
An integration is determined to be for a third-party module if it cannot
be imported in the environment in which the tests run.
"""
parts = integration_submodule_name.split(".")
module_path = pathlib.Path("sentry_sdk") / "integrations" / pathlib.Path(*parts)
import_path = ".".join(("sentry_sdk", "integrations", *parts))
integration_dependencies = set()
for py_file in pathlib.Path(module_path).rglob("*.py"):
source = py_file.read_text(encoding="utf-8")
tree = ast.parse(source, filename=str(py_file))
integration_dependencies.update(find_unrecognized_dependencies(tree))
mod = None
try:
# If importing the integration succeeds in the current environment, assume
# that the integration has no non-standard imports.
mod = importlib.import_module(import_path)
except integrations.DidNotEnable:
# For each non-standard import, create an empty shadow module to
# emulate an empty "agents.py" or analogous local module that
# shadows the package.
for dependency in integration_dependencies:
sys.modules[dependency] = types.ModuleType(dependency)
# Importing the integration must raise DidNotEnable, since the
# SDK catches the exception type when attempting to activate
# auto-enabling integrations.
with pytest.raises(integrations.DidNotEnable):
importlib.import_module(import_path)
for dependency in integration_dependencies:
del sys.modules[dependency]
# `setup_once()` can also raise when initializing the SDK with a shadowed module.
if mod is not None:
# For each non-standard import, create an empty shadow module to
# emulate an empty "agents.py" or analogous local module that
# shadows the package.
for dependency in integration_dependencies:
sys.modules[dependency] = types.ModuleType(dependency)
integration_types = [
v
for v in mod.__dict__.values()
if isinstance(v, type) and issubclass(v, Integration)
]
for integration in integration_types:
# The `setup_once()` method should only raise DidNotEnable.
try:
integration.setup_once()
except integrations.DidNotEnable:
pass
for dependency in integration_dependencies:
del sys.modules[dependency]