import logging from unittest.mock import Mock import pytest from sanic import Sanic from sanic.response import text from sanic.server.async_server import AsyncioServer from sanic.signals import Event from sanic.touchup.schemes.ode import OptionalDispatchEvent try: from unittest.mock import AsyncMock except ImportError: from tests.asyncmock import AsyncMock # type: ignore @pytest.fixture def app_one(): app = Sanic("One") @app.get("/one") async def one(request): return text("one") return app @pytest.fixture def app_two(): app = Sanic("Two") @app.get("/two") async def two(request): return text("two") return app @pytest.fixture(autouse=True) def clean(): Sanic._app_registry = {} yield def test_serve_same_app_multiple_tuples(app_one, run_multi): app_one.prepare(port=23456) app_one.prepare(port=23457) logs = run_multi(app_one) assert ( "sanic.root", logging.INFO, "Goin' Fast @ http://127.0.0.1:23456", ) in logs assert ( "sanic.root", logging.INFO, "Goin' Fast @ http://127.0.0.1:23457", ) in logs def test_serve_multiple_apps(app_one, app_two, run_multi): app_one.prepare(port=23456) app_two.prepare(port=23457) logs = run_multi(app_one) assert ( "sanic.root", logging.INFO, "Goin' Fast @ http://127.0.0.1:23456", ) in logs assert ( "sanic.root", logging.INFO, "Goin' Fast @ http://127.0.0.1:23457", ) in logs def test_listeners_on_secondary_app(app_one, app_two, run_multi): app_one.prepare(port=23456) app_two.prepare(port=23457) before_start = AsyncMock() after_start = AsyncMock() before_stop = AsyncMock() after_stop = AsyncMock() app_two.before_server_start(before_start) app_two.after_server_start(after_start) app_two.before_server_stop(before_stop) app_two.after_server_stop(after_stop) run_multi(app_one) before_start.assert_awaited_once() after_start.assert_awaited_once() before_stop.assert_awaited_once() after_stop.assert_awaited_once() @pytest.mark.parametrize( "events", ( (Event.HTTP_LIFECYCLE_BEGIN,), (Event.HTTP_LIFECYCLE_BEGIN, Event.HTTP_LIFECYCLE_COMPLETE), ( Event.HTTP_LIFECYCLE_BEGIN, Event.HTTP_LIFECYCLE_COMPLETE, Event.HTTP_LIFECYCLE_REQUEST, ), ), ) def test_signal_synchronization(app_one, app_two, run_multi, events): app_one.prepare(port=23456) app_two.prepare(port=23457) for event in events: app_one.signal(event)(AsyncMock()) run_multi(app_one) assert len(app_two.signal_router.routes) == len(events) + 1 signal_handlers = { signal.handler for signal in app_two.signal_router.routes if signal.name.startswith("http") } assert len(signal_handlers) == 1 assert list(signal_handlers)[0] is OptionalDispatchEvent.noop def test_warning_main_process_listeners_on_secondary( app_one, app_two, run_multi ): app_two.main_process_start(AsyncMock()) app_two.main_process_stop(AsyncMock()) app_one.prepare(port=23456) app_two.prepare(port=23457) log = run_multi(app_one) message = ( f"Sanic found 2 listener(s) on " "secondary applications attached to the main " "process. These will be ignored since main " "process listeners can only be attached to your " "primary application: " f"{repr(app_one)}" ) assert ("sanic.error", logging.WARNING, message) in log def test_no_applications(): Sanic._app_registry = {} message = "Did not find any applications." with pytest.raises(RuntimeError, match=message): Sanic.serve() def test_oserror_warning(app_one, app_two, run_multi, capfd): orig = AsyncioServer.__await__ AsyncioServer.__await__ = Mock(side_effect=OSError("foo")) app_one.prepare(port=23456, workers=2) app_two.prepare(port=23457, workers=2) run_multi(app_one) captured = capfd.readouterr() assert ( "An OSError was detected on startup. The encountered error was: foo" ) in captured.err AsyncioServer.__await__ = orig def test_running_multiple_offset_warning(app_one, app_two, run_multi, capfd): app_one.prepare(port=23456, workers=2) app_two.prepare(port=23457) run_multi(app_one) captured = capfd.readouterr() assert ( f"The primary application {repr(app_one)} is running " "with 2 worker(s). All " "application instances will run with the same number. " f"You requested {repr(app_two)} to run with " "1 worker(s), which will be ignored " "in favor of the primary application." ) in captured.err def test_running_multiple_secondary(app_one, app_two, run_multi, capfd): app_one.prepare(port=23456, workers=2) app_two.prepare(port=23457) before_start = AsyncMock() app_two.before_server_start(before_start) run_multi(app_one) before_start.await_count == 2