gunicorn/tests/docker/uwsgi/conftest.py
Benoit Chesneau ecc471f3b4 tests: Add Docker integration tests for uWSGI protocol with nginx
Add comprehensive integration tests verifying gunicorn's uWSGI binary
protocol works correctly with nginx's uwsgi_pass directive.

Test categories:
- Basic GET/POST requests with query strings and large bodies
- Header preservation (custom headers, Host, Content-Type)
- HTTP keep-alive connections
- Error responses (400-503 status codes)
- WSGI environ variables
- Large response streaming (1MB)
- Concurrent request handling
- Edge cases (binary data, unicode, long headers)

Architecture: pytest -> nginx:8080 -> uwsgi_pass -> gunicorn:8000

Also adds GitHub Actions workflow that runs on changes to uwsgi module
or docker test files.
2026-01-22 19:06:30 +01:00

122 lines
3.1 KiB
Python

"""
pytest fixtures for uWSGI Docker integration tests.
"""
import os
import subprocess
import time
import pytest
import requests
COMPOSE_FILE = os.path.join(os.path.dirname(__file__), 'docker-compose.yml')
NGINX_URL = 'http://127.0.0.1:8080'
STARTUP_TIMEOUT = 60 # seconds
def is_docker_available():
"""Check if Docker is available."""
try:
result = subprocess.run(
['docker', 'info'],
capture_output=True,
timeout=10
)
return result.returncode == 0
except (subprocess.TimeoutExpired, FileNotFoundError):
return False
def is_compose_available():
"""Check if docker compose is available."""
try:
result = subprocess.run(
['docker', 'compose', 'version'],
capture_output=True,
timeout=10
)
return result.returncode == 0
except (subprocess.TimeoutExpired, FileNotFoundError):
return False
docker_available = pytest.mark.skipif(
not is_docker_available() or not is_compose_available(),
reason="Docker or docker compose not available"
)
@pytest.fixture(scope='session')
def docker_services():
"""
Start Docker Compose services for the test session.
This fixture builds and starts the gunicorn and nginx containers,
waits for them to be healthy, and tears them down after all tests.
"""
if not is_docker_available() or not is_compose_available():
pytest.skip("Docker or docker compose not available")
# Build and start services
subprocess.run(
['docker', 'compose', '-f', COMPOSE_FILE, 'build'],
check=True,
capture_output=True
)
subprocess.run(
['docker', 'compose', '-f', COMPOSE_FILE, 'up', '-d'],
check=True,
capture_output=True
)
# Wait for services to be healthy
start_time = time.time()
while time.time() - start_time < STARTUP_TIMEOUT:
try:
response = requests.get(f'{NGINX_URL}/', timeout=2)
if response.status_code == 200:
break
except requests.RequestException:
pass
time.sleep(1)
else:
# Get logs for debugging
logs = subprocess.run(
['docker', 'compose', '-f', COMPOSE_FILE, 'logs'],
capture_output=True,
text=True
)
subprocess.run(
['docker', 'compose', '-f', COMPOSE_FILE, 'down', '-v'],
capture_output=True
)
pytest.fail(
f"Services did not become healthy within {STARTUP_TIMEOUT}s.\n"
f"Logs:\n{logs.stdout}\n{logs.stderr}"
)
yield
# Teardown
subprocess.run(
['docker', 'compose', '-f', COMPOSE_FILE, 'down', '-v'],
capture_output=True
)
@pytest.fixture
def nginx_url(docker_services):
"""Return the nginx base URL."""
return NGINX_URL
@pytest.fixture
def session(docker_services):
"""Return a requests Session with keep-alive enabled."""
with requests.Session() as s:
# Enable keep-alive
s.headers['Connection'] = 'keep-alive'
yield s