Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 39 additions & 10 deletions tests/test_shadowed_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def pytest_generate_tests(metafunc):
submodule_names = {
submodule_name
for _, submodule_name, _ in pkgutil.walk_packages(integrations.__path__)
}
} - {"beam", "spark", "unraisablehook"}

metafunc.parametrize(
"integration_submodule_name",
Expand Down Expand Up @@ -79,18 +79,23 @@ def test_shadowed_modules_when_importing_integrations(
An integration is determined to be for a third-party module if it cannot
be imported in the environment in which the tests run.
"""
module_path = f"sentry_sdk.integrations.{integration_submodule_name}"
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"):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Path construction fails for single-file integrations

High Severity

The module_path is constructed without the .py extension for single-file integrations like aiohttp. When rglob is called on sentry_sdk/integrations/aiohttp (a non-existent directory since the actual file is aiohttp.py), it returns an empty iterator. This causes integration_dependencies to remain empty, making the test ineffective for single-file integrations—it doesn't actually test them with shadowed dependencies.

Fix in Cursor Fix in Web

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.
importlib.import_module(module_path)
return
except integrations.DidNotEnable:
spec = importlib.util.find_spec(module_path)
source = pathlib.Path(spec.origin).read_text(encoding="utf-8")
tree = ast.parse(source, filename=spec.origin)
integration_dependencies = find_unrecognized_dependencies(tree)
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.
Expand All @@ -101,7 +106,31 @@ def test_shadowed_modules_when_importing_integrations(
# SDK catches the exception type when attempting to activate
# auto-enabling integrations.
with pytest.raises(integrations.DidNotEnable):
importlib.import_module(module_path)
importlib.import_module(import_path)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shadow modules leak if reimport assertion fails

Medium Severity

If the pytest.raises assertion fails because importlib.import_module(import_path) doesn't raise DidNotEnable as expected, the assertion failure exception prevents the cleanup code at lines 111-112 from executing. This leaves shadow modules in sys.modules that could interfere with subsequent tests.

Fix in Cursor Fix in Web


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]
Loading