From 128461ae095c136cc130931cafc44b99b3790afe Mon Sep 17 00:00:00 2001 From: ali ugur Date: Fri, 29 Nov 2024 14:33:33 +0300 Subject: [PATCH] Chore(test): Improve tests and gevent module check --- .../django_async_app/settings.py | 2 +- .../django_async_app/migrate.sh | 5 --- src/paas_charm/_gunicorn/charm.py | 32 +++++++++++-------- tests/unit/django/test_workers.py | 31 +++++++++++------- tests/unit/flask/test_workers.py | 30 ++++++++++------- 5 files changed, 57 insertions(+), 43 deletions(-) delete mode 100644 examples/django/django_async_app/django_async_app/migrate.sh diff --git a/examples/django/django_async_app/django_async_app/django_async_app/settings.py b/examples/django/django_async_app/django_async_app/django_async_app/settings.py index 24b5e10..614e67f 100644 --- a/examples/django/django_async_app/django_async_app/django_async_app/settings.py +++ b/examples/django/django_async_app/django_async_app/django_async_app/settings.py @@ -31,7 +31,7 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = os.environ.get("DJANGO_DEBUG", "true") == "true" -ALLOWED_HOSTS = json.loads(os.environ.get("DJANGO_ALLOWED_HOSTS", '["*"]')) +ALLOWED_HOSTS = json.loads(os.environ["DJANGO_ALLOWED_HOSTS"]) INSTALLED_APPS = [ diff --git a/examples/django/django_async_app/django_async_app/migrate.sh b/examples/django/django_async_app/django_async_app/migrate.sh deleted file mode 100644 index ce3a73c..0000000 --- a/examples/django/django_async_app/django_async_app/migrate.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/bash -# Copyright 2024 Canonical Ltd. -# See LICENSE file for licensing details. - -python3 manage.py migrate diff --git a/src/paas_charm/_gunicorn/charm.py b/src/paas_charm/_gunicorn/charm.py index c1a94a9..a81187a 100644 --- a/src/paas_charm/_gunicorn/charm.py +++ b/src/paas_charm/_gunicorn/charm.py @@ -5,6 +5,8 @@ import logging +from ops.pebble import ExecError, ExecProcess + from paas_charm._gunicorn.webserver import GunicornWebserver, WebserverConfig, WorkerClassEnum from paas_charm._gunicorn.workload_config import create_workload_config from paas_charm._gunicorn.wsgi_app import WsgiApp @@ -25,18 +27,6 @@ def _workload_config(self) -> WorkloadConfig: framework_name=self._framework_name, unit_name=self.unit.name ) - def check_gevent_package(self) -> bool: - """Check that gevent is installed. - - Returns: - True if gevent is installed. - """ - pip_list_command = self._container.exec( - ["python3", "-c", "'import gevent;print(gevent.__version__)'"] - ) - list_output = pip_list_command.wait_output()[0] - return "ModuleNotFoundError" not in list_output - def create_webserver_config(self) -> WebserverConfig: """Validate worker_class and create a WebserverConfig instance from the charm config. @@ -68,7 +58,7 @@ def create_webserver_config(self) -> WebserverConfig: if worker_class is WorkerClassEnum.SYNC: return webserver_config - if not self.check_gevent_package(): + if not self._check_gevent_package(): logger.error( "gunicorn[gevent] must be installed in the rock. %s", doc_link, @@ -100,3 +90,19 @@ def _create_app(self) -> App: webserver=webserver, database_migration=self._database_migration, ) + + def _check_gevent_package(self) -> bool: + """Check that gevent is installed. + + Returns: + True if gevent is installed. + """ + try: + check_gevent_process: ExecProcess = self._container.exec( + ["python3", "-c", "import gevent"] + ) + check_gevent_process.wait_output() + return True + except ExecError as cmd_error: + logger.warning("gunicorn[gevent] install check failed: %s", cmd_error) + return False diff --git a/tests/unit/django/test_workers.py b/tests/unit/django/test_workers.py index 02cea0b..e4c605b 100644 --- a/tests/unit/django/test_workers.py +++ b/tests/unit/django/test_workers.py @@ -5,24 +5,27 @@ import ops import pytest -from ops.testing import Harness +from ops.testing import ExecResult, Harness from .constants import DEFAULT_LAYER @pytest.mark.parametrize( - "worker_class, expected_status, expected_message", + "worker_class, expected_status, expected_message, exec_res", [ ( "eventlet", "blocked", "Only 'gevent' and 'sync' are allowed. https://bit.ly/django-async-doc", + 1, ), - ("gevent", "active", ""), - ("sync", "active", ""), + ("gevent", "active", "", 0), + ("sync", "active", "", 0), ], ) -def test_async_workers_config(harness: Harness, worker_class, expected_status, expected_message): +def test_async_workers_config( + harness: Harness, worker_class, expected_status, expected_message, exec_res +): """ arrange: Prepare a unit and run initial hooks. act: Set the `webserver-worker-class` config. @@ -38,11 +41,13 @@ def test_async_workers_config(harness: Harness, worker_class, expected_status, e harness.add_relation("postgresql", "postgresql-k8s", app_data=postgresql_relation_data) container = harness.model.unit.get_container("django-app") container.add_layer("a_layer", DEFAULT_LAYER) + harness.handle_exec( container.name, - ["python3", "-c", "'import gevent;print(gevent.__version__)'"], - result="Gevent", + ["python3", "-c", "import gevent"], + result=ExecResult(exit_code=exec_res), ) + harness.begin_with_initial_hooks() harness.update_config({"webserver-worker-class": worker_class}) assert harness.model.unit.status == ops.StatusBase.from_name( @@ -51,23 +56,25 @@ def test_async_workers_config(harness: Harness, worker_class, expected_status, e @pytest.mark.parametrize( - "worker_class, expected_status, expected_message", + "worker_class, expected_status, expected_message, exec_res", [ ( "eventlet", "blocked", "Only 'gevent' and 'sync' are allowed. https://bit.ly/django-async-doc", + 1, ), ( "gevent", "blocked", "gunicorn[gevent] must be installed in the rock. https://bit.ly/django-async-doc", + 1, ), - ("sync", "active", ""), + ("sync", "active", "", 0), ], ) def test_async_workers_config_fail( - harness: Harness, worker_class, expected_status, expected_message + harness: Harness, worker_class, expected_status, expected_message, exec_res ): """ arrange: Prepare a unit and run initial hooks. @@ -86,8 +93,8 @@ def test_async_workers_config_fail( container.add_layer("a_layer", DEFAULT_LAYER) harness.handle_exec( container.name, - ["python3", "-c", "'import gevent;print(gevent.__version__)'"], - result="ModuleNotFoundError", + ["python3", "-c", "import gevent"], + result=ExecResult(exit_code=exec_res), ) harness.begin_with_initial_hooks() harness.update_config({"webserver-worker-class": worker_class}) diff --git a/tests/unit/flask/test_workers.py b/tests/unit/flask/test_workers.py index 51e62ec..9e0d077 100644 --- a/tests/unit/flask/test_workers.py +++ b/tests/unit/flask/test_workers.py @@ -9,7 +9,7 @@ import ops import pytest -from ops.testing import Harness +from ops.testing import ExecResult, Harness from .constants import DEFAULT_LAYER, FLASK_CONTAINER_NAME, LAYER_WITH_WORKER @@ -40,18 +40,21 @@ def test_worker(harness: Harness): @pytest.mark.parametrize( - "worker_class, expected_status, expected_message", + "worker_class, expected_status, expected_message, exec_res", [ ( "eventlet", "blocked", "Only 'gevent' and 'sync' are allowed. https://bit.ly/flask-async-doc", + 1, ), - ("gevent", "active", ""), - ("sync", "active", ""), + ("gevent", "active", "", 0), + ("sync", "active", "", 0), ], ) -def test_async_workers_config(harness: Harness, worker_class, expected_status, expected_message): +def test_async_workers_config( + harness: Harness, worker_class, expected_status, expected_message, exec_res +): """ arrange: Prepare a unit and run initial hooks. act: Set the `webserver-worker-class` config. @@ -63,9 +66,10 @@ def test_async_workers_config(harness: Harness, worker_class, expected_status, e harness.handle_exec( container.name, - ["python3", "-c", "'import gevent;print(gevent.__version__)'"], - result="Gevent", + ["python3", "-c", "import gevent"], + result=ExecResult(exit_code=exec_res), ) + harness.begin_with_initial_hooks() harness.update_config({"webserver-worker-class": worker_class}) assert harness.model.unit.status == ops.StatusBase.from_name( @@ -74,23 +78,25 @@ def test_async_workers_config(harness: Harness, worker_class, expected_status, e @pytest.mark.parametrize( - "worker_class, expected_status, expected_message", + "worker_class, expected_status, expected_message, exec_res", [ ( "eventlet", "blocked", "Only 'gevent' and 'sync' are allowed. https://bit.ly/flask-async-doc", + 1, ), ( "gevent", "blocked", "gunicorn[gevent] must be installed in the rock. https://bit.ly/flask-async-doc", + 1, ), - ("sync", "active", ""), + ("sync", "active", "", 0), ], ) def test_async_workers_config_fail( - harness: Harness, worker_class, expected_status, expected_message + harness: Harness, worker_class, expected_status, expected_message, exec_res ): """ arrange: Prepare a unit and run initial hooks. @@ -103,8 +109,8 @@ def test_async_workers_config_fail( harness.handle_exec( container.name, - ["python3", "-c", "'import gevent;print(gevent.__version__)'"], - result="ModuleNotFoundError", + ["python3", "-c", "import gevent"], + result=ExecResult(exit_code=exec_res), ) harness.begin_with_initial_hooks() harness.update_config({"webserver-worker-class": worker_class})