# # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. from unittest import mock import pytest try: import gevent HAS_GEVENT = True except ImportError: HAS_GEVENT = False pytestmark = pytest.mark.skipif(not HAS_GEVENT, reason="gevent not installed") def test_import(): __import__('gunicorn.workers.ggevent') def test_version_requirement(): """Test that gevent 24.10.1+ is required.""" from gunicorn.workers import ggevent from packaging.version import parse as parse_version assert parse_version(gevent.__version__) >= parse_version('24.10.1') class TestGeventWorkerInit: """Test GeventWorker initialization.""" def test_worker_has_no_server_class(self): """Test that GeventWorker has no server_class by default.""" from gunicorn.workers.ggevent import GeventWorker assert GeventWorker.server_class is None def test_worker_has_no_wsgi_handler(self): """Test that GeventWorker has no wsgi_handler by default.""" from gunicorn.workers.ggevent import GeventWorker assert GeventWorker.wsgi_handler is None def test_init_process_patches_and_reinits(self): """Test that init_process calls patch and reinits the hub.""" from gunicorn.workers.ggevent import GeventWorker worker = mock.Mock(spec=GeventWorker) worker.sockets = [] with mock.patch('gunicorn.workers.ggevent.hub') as mock_hub, \ mock.patch.object(GeventWorker.__bases__[0], 'init_process'): GeventWorker.init_process(worker) # Verify patch was called worker.patch.assert_called_once() mock_hub.reinit.assert_called_once() class TestGeventWorkerRun: """Test GeventWorker run method.""" def test_run_creates_stream_servers(self): """Test that run creates StreamServer instances for each socket.""" from gunicorn.workers.ggevent import GeventWorker worker = mock.Mock(spec=GeventWorker) worker.sockets = [mock.Mock()] worker.cfg = mock.Mock(is_ssl=False, workers=1, graceful_timeout=30) worker.server_class = None worker.worker_connections = 1000 # Make alive return True once, then False to exit the loop worker.alive = False with mock.patch('gunicorn.workers.ggevent.Pool') as mock_pool, \ mock.patch('gunicorn.workers.ggevent.StreamServer') as mock_server_cls, \ mock.patch('gunicorn.workers.ggevent.gevent') as mock_gevent: mock_server = mock.Mock() mock_server.pool = mock.Mock() mock_server.pool.free_count.return_value = mock_server.pool.size mock_server_cls.return_value = mock_server GeventWorker.run(worker) mock_server_cls.assert_called_once() mock_server.start.assert_called_once() mock_server.close.assert_called_once() def test_run_with_ssl(self): """Test that run configures SSL context when is_ssl is True.""" from gunicorn.workers.ggevent import GeventWorker worker = mock.Mock(spec=GeventWorker) worker.sockets = [mock.Mock()] worker.cfg = mock.Mock(is_ssl=True, workers=1, graceful_timeout=30) worker.server_class = None worker.worker_connections = 1000 worker.alive = False with mock.patch('gunicorn.workers.ggevent.Pool'), \ mock.patch('gunicorn.workers.ggevent.StreamServer') as mock_server_cls, \ mock.patch('gunicorn.workers.ggevent.gevent'), \ mock.patch('gunicorn.workers.ggevent.ssl_context') as mock_ssl_ctx: mock_server = mock.Mock() mock_server.pool = mock.Mock() mock_server.pool.free_count.return_value = mock_server.pool.size mock_server_cls.return_value = mock_server mock_ssl_ctx.return_value = mock.Mock() GeventWorker.run(worker) mock_ssl_ctx.assert_called_once_with(worker.cfg) # Verify ssl_context was passed to StreamServer call_kwargs = mock_server_cls.call_args[1] assert 'ssl_context' in call_kwargs class TestSignalHandling: """Test signal handling in GeventWorker.""" def test_handle_quit_spawns_greenlet(self): """Test that handle_quit spawns a greenlet instead of blocking.""" from gunicorn.workers.ggevent import GeventWorker worker = mock.Mock(spec=GeventWorker) with mock.patch('gunicorn.workers.ggevent.gevent') as mock_gevent: GeventWorker.handle_quit(worker, mock.Mock(), mock.Mock()) mock_gevent.spawn.assert_called_once() def test_handle_usr1_spawns_greenlet(self): """Test that handle_usr1 spawns a greenlet instead of blocking.""" from gunicorn.workers.ggevent import GeventWorker worker = mock.Mock(spec=GeventWorker) with mock.patch('gunicorn.workers.ggevent.gevent') as mock_gevent: GeventWorker.handle_usr1(worker, mock.Mock(), mock.Mock()) mock_gevent.spawn.assert_called_once() def test_notify_exits_on_parent_change(self): """Test that notify exits when parent PID changes.""" from gunicorn.workers.ggevent import GeventWorker worker = mock.Mock(spec=GeventWorker) worker.ppid = 1234 worker.log = mock.Mock() with mock.patch('gunicorn.workers.ggevent.os') as mock_os, \ mock.patch.object(GeventWorker.__bases__[0], 'notify'): mock_os.getppid.return_value = 5678 # Different PID with pytest.raises(SystemExit): GeventWorker.notify(worker) class TestPyWSGIWorker: """Test PyWSGI-based worker classes.""" def test_pywsgi_worker_has_server_class(self): """Test that GeventPyWSGIWorker has proper server_class.""" from gunicorn.workers.ggevent import GeventPyWSGIWorker, PyWSGIServer assert GeventPyWSGIWorker.server_class is PyWSGIServer def test_pywsgi_worker_has_handler(self): """Test that GeventPyWSGIWorker has proper wsgi_handler.""" from gunicorn.workers.ggevent import GeventPyWSGIWorker, PyWSGIHandler assert GeventPyWSGIWorker.wsgi_handler is PyWSGIHandler def test_pywsgi_handler_get_environ(self): """Test that PyWSGIHandler adds gunicorn-specific environ keys.""" from gunicorn.workers.ggevent import PyWSGIHandler handler = mock.Mock(spec=PyWSGIHandler) handler.socket = mock.Mock() handler.path = '/test/path' # Mock the parent get_environ with mock.patch.object(PyWSGIHandler.__bases__[0], 'get_environ', return_value={}): env = PyWSGIHandler.get_environ(handler) assert env['gunicorn.sock'] == handler.socket assert env['RAW_URI'] == '/test/path' class TestGeventResponse: """Test GeventResponse helper class.""" def test_response_attributes(self): """Test GeventResponse stores status, headers, and sent.""" from gunicorn.workers.ggevent import GeventResponse resp = GeventResponse('200 OK', {'Content-Type': 'text/html'}, 1024) assert resp.status == '200 OK' assert resp.headers == {'Content-Type': 'text/html'} assert resp.sent == 1024 class TestTimeoutContext: """Test timeout context manager.""" def test_timeout_ctx_uses_keepalive(self): """Test that timeout_ctx uses cfg.keepalive.""" from gunicorn.workers.ggevent import GeventWorker worker = mock.Mock(spec=GeventWorker) worker.cfg = mock.Mock(keepalive=30) with mock.patch('gunicorn.workers.ggevent.gevent') as mock_gevent: mock_timeout = mock.Mock() mock_gevent.Timeout.return_value = mock_timeout result = GeventWorker.timeout_ctx(worker) mock_gevent.Timeout.assert_called_once_with(30, False) assert result == mock_timeout