diff --git a/tests/conftest.py b/tests/conftest.py index 64527c1e36..ec2d04c7ce 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,8 @@ +import contextlib import json import os import socket +import time import warnings from threading import Thread from contextlib import contextmanager @@ -20,6 +22,11 @@ except ImportError: eventlet = None +try: + import uvicorn +except ImportError: + uvicorn = None + import sentry_sdk import sentry_sdk.utils from sentry_sdk.envelope import Envelope @@ -645,3 +652,31 @@ def __eq__(self, other): def __ne__(self, other): return not self.__eq__(other) + + +if uvicorn is not None: + + class UvicornServer(uvicorn.Server): + @contextlib.contextmanager + def run_in_thread(self): + thread = Thread(target=self.run) + thread.start() + try: + while not self.started: + time.sleep(1e-3) + yield + finally: + self.should_exit = True + thread.join() + + @pytest.fixture(scope="session") + def uvicorn_server(request): + app = request.param() + + config = uvicorn.Config( + app, host="127.0.0.1", port=5000, log_level="info", loop="none" + ) + server = UvicornServer(config=config) + + with server.run_in_thread(): + yield diff --git a/tests/integrations/starlette/test_starlette.py b/tests/integrations/starlette/test_starlette.py index fd47895f5a..636cc0adb2 100644 --- a/tests/integrations/starlette/test_starlette.py +++ b/tests/integrations/starlette/test_starlette.py @@ -10,6 +10,7 @@ from unittest import mock import pytest +import requests from sentry_sdk import capture_message, get_baggage, get_traceparent from sentry_sdk.integrations.asgi import SentryAsgiMiddleware @@ -102,6 +103,9 @@ def starlette_app_factory(middleware=None, debug=True): ) templates = Jinja2Templates(directory=template_dir) + async def _ok(request): + return starlette.responses.JSONResponse({"status": "ok"}) + async def _homepage(request): 1 / 0 return starlette.responses.JSONResponse({"status": "ok"}) @@ -160,6 +164,7 @@ async def _render_template(request): app = starlette.applications.Starlette( debug=debug, routes=[ + starlette.routing.Route("/ok", _ok), starlette.routing.Route("/some_url", _homepage), starlette.routing.Route("/custom_error", _custom_error), starlette.routing.Route("/message", _message), @@ -1135,6 +1140,23 @@ def test_transaction_name_in_middleware( ) +@pytest.mark.parametrize("uvicorn_server", [starlette_app_factory], indirect=True) +def test_with_uvicorn(sentry_init, capture_envelopes, uvicorn_server): + # Sanity check that the app works with uvicorn which does its own ASGI 2/3 + # discovery. If we wrap the ASGI app incorrectly, everything might seem ok + # until you try to run the app with uvicorn. + + sentry_init( + integrations=[StarletteIntegration()], + ) + + envelopes = capture_envelopes() + + requests.get("http://127.0.0.1:5000/ok") + + assert not envelopes + + def test_span_origin(sentry_init, capture_events): sentry_init( integrations=[StarletteIntegration()], diff --git a/tox.ini b/tox.ini index 6acff6b8e8..45bfedc93c 100644 --- a/tox.ini +++ b/tox.ini @@ -655,12 +655,12 @@ deps = starlette: pytest-asyncio starlette: python-multipart starlette: requests + starlette: uvicorn starlette: httpx # (this is a dependency of httpx) starlette: anyio<4.0.0 starlette: jinja2 starlette-v0.19: starlette~=0.19.0 - starlette-v0.20: starlette~=0.20.0 starlette-v0.24: starlette~=0.24.0 starlette-v0.28: starlette~=0.28.0 starlette-v0.32: starlette~=0.32.0