11import logging
2+ import multiprocessing
23import os
34from pathlib import Path
45from typing import Any , Dict
78from _pytest .monkeypatch import MonkeyPatch
89from pytest_mock import MockerFixture
910
10- from inboard import start
11+ from inboard import gunicorn_conf , start
1112
1213
1314class TestConfPaths :
@@ -41,6 +42,48 @@ def test_set_incorrect_conf_path(self, monkeypatch: MonkeyPatch) -> None:
4142 start .set_conf_path ("gunicorn" )
4243
4344
45+ class TestConfigureGunicorn :
46+ """Test Gunicorn configuration independently of Gunicorn server.
47+ ---
48+ """
49+
50+ def test_gunicorn_conf_workers_default (self ) -> None :
51+ """Test default number of Gunicorn worker processes."""
52+ assert gunicorn_conf .workers >= 2
53+ assert gunicorn_conf .workers == multiprocessing .cpu_count ()
54+
55+ def test_gunicorn_conf_workers_custom (self , monkeypatch : MonkeyPatch ) -> None :
56+ """Test custom Gunicorn worker process calculation."""
57+ monkeypatch .setenv ("MAX_WORKERS" , "1" )
58+ monkeypatch .setenv ("WEB_CONCURRENCY" , "4" )
59+ monkeypatch .setenv ("WORKERS_PER_CORE" , "0.5" )
60+ assert os .getenv ("MAX_WORKERS" ) == "1"
61+ assert os .getenv ("WEB_CONCURRENCY" ) == "4"
62+ assert os .getenv ("WORKERS_PER_CORE" ) == "0.5"
63+ assert (
64+ gunicorn_conf .calculate_workers (
65+ str (os .getenv ("MAX_WORKERS" )),
66+ str (os .getenv ("WEB_CONCURRENCY" )),
67+ str (os .getenv ("WORKERS_PER_CORE" )),
68+ )
69+ == 1
70+ )
71+ monkeypatch .delenv ("MAX_WORKERS" )
72+ assert (
73+ gunicorn_conf .calculate_workers (
74+ None ,
75+ str (os .getenv ("WEB_CONCURRENCY" )),
76+ str (os .getenv ("WORKERS_PER_CORE" )),
77+ )
78+ == 4
79+ )
80+ monkeypatch .delenv ("WEB_CONCURRENCY" )
81+ cores : int = multiprocessing .cpu_count ()
82+ assert gunicorn_conf .calculate_workers (
83+ None , "2" , str (os .getenv ("WORKERS_PER_CORE" )), cores = cores
84+ ) == int (cores * 0.5 )
85+
86+
4487class TestConfigureLogging :
4588 """Test logging configuration methods.
4689 ---
@@ -393,6 +436,7 @@ def test_start_server_uvicorn_gunicorn(
393436 f"--worker-tmp-dir { tmp_path } " ,
394437 )
395438 monkeypatch .setenv ("PROCESS_MANAGER" , "gunicorn" )
439+ assert gunicorn_conf_path .parent .exists ()
396440 assert os .getenv ("GUNICORN_CONF" ) == str (gunicorn_conf_path )
397441 assert os .getenv ("PROCESS_MANAGER" ) == "gunicorn"
398442 mock_run = mocker .patch ("subprocess.run" , autospec = True )
@@ -431,7 +475,7 @@ def test_start_server_uvicorn_gunicorn_custom_config(
431475 mocker : MockerFixture ,
432476 monkeypatch : MonkeyPatch ,
433477 ) -> None :
434- """Test `start.start_server` with Uvicorn managed by Gunicorn."""
478+ """Test customized `start.start_server` with Uvicorn managed by Gunicorn."""
435479 monkeypatch .setenv (
436480 "GUNICORN_CMD_ARGS" ,
437481 f"--worker-tmp-dir { gunicorn_conf_tmp_file_path .parent } " ,
@@ -441,12 +485,23 @@ def test_start_server_uvicorn_gunicorn_custom_config(
441485 monkeypatch .setenv ("MAX_WORKERS" , "1" )
442486 monkeypatch .setenv ("PROCESS_MANAGER" , "gunicorn" )
443487 monkeypatch .setenv ("WEB_CONCURRENCY" , "4" )
488+ monkeypatch .setenv ("WORKERS_PER_CORE" , "0.5" )
489+ assert gunicorn_conf_tmp_file_path .parent .exists ()
444490 assert os .getenv ("GUNICORN_CONF" ) == str (gunicorn_conf_tmp_file_path )
445491 assert os .getenv ("LOG_FORMAT" ) == "gunicorn"
446492 assert os .getenv ("LOG_LEVEL" ) == "debug"
447493 assert os .getenv ("MAX_WORKERS" ) == "1"
448494 assert os .getenv ("PROCESS_MANAGER" ) == "gunicorn"
449495 assert os .getenv ("WEB_CONCURRENCY" ) == "4"
496+ assert os .getenv ("WORKERS_PER_CORE" ) == "0.5"
497+ assert (
498+ gunicorn_conf .calculate_workers (
499+ str (os .getenv ("MAX_WORKERS" )),
500+ str (os .getenv ("WEB_CONCURRENCY" )),
501+ str (os .getenv ("WORKERS_PER_CORE" )),
502+ )
503+ == 1
504+ )
450505 mock_run = mocker .patch ("subprocess.run" , autospec = True )
451506 start .start_server (
452507 str (os .getenv ("PROCESS_MANAGER" )),
0 commit comments