From 6575d86251ff5385bbe8e7016357fa7792aebd86 Mon Sep 17 00:00:00 2001 From: Benoit Chesneau Date: Sun, 25 Jan 2026 15:03:12 +0100 Subject: [PATCH] Add docker integration tests for simple ASGI (HTTP protocol) Tests the ASGI worker with direct HTTP requests without uWSGI protocol. Includes tests for GET, POST, query strings, path handling, keepalive, large bodies, and custom headers. --- tests/docker/asgi/Dockerfile | 18 +++++ tests/docker/asgi/app.py | 45 +++++++++++ tests/docker/asgi/docker-compose.yml | 14 ++++ tests/docker/asgi/test_asgi.sh | 116 +++++++++++++++++++++++++++ 4 files changed, 193 insertions(+) create mode 100644 tests/docker/asgi/Dockerfile create mode 100644 tests/docker/asgi/app.py create mode 100644 tests/docker/asgi/docker-compose.yml create mode 100755 tests/docker/asgi/test_asgi.sh diff --git a/tests/docker/asgi/Dockerfile b/tests/docker/asgi/Dockerfile new file mode 100644 index 00000000..57361aa6 --- /dev/null +++ b/tests/docker/asgi/Dockerfile @@ -0,0 +1,18 @@ +FROM python:3.11-slim + +WORKDIR /build + +# Copy gunicorn source +COPY . /build/ + +# Install gunicorn from source +RUN pip install --no-cache-dir -e . + +# Copy test app +WORKDIR /app +COPY tests/docker/asgi/app.py /app/ + +# Expose HTTP port +EXPOSE 8000 + +CMD ["gunicorn", "--worker-class", "asgi", "--bind", "0.0.0.0:8000", "app:app"] diff --git a/tests/docker/asgi/app.py b/tests/docker/asgi/app.py new file mode 100644 index 00000000..699d3577 --- /dev/null +++ b/tests/docker/asgi/app.py @@ -0,0 +1,45 @@ +"""Simple ASGI test application for HTTP protocol testing.""" + + +async def app(scope, receive, send): + """Simple ASGI application that echoes request info.""" + if scope["type"] == "lifespan": + while True: + message = await receive() + if message["type"] == "lifespan.startup": + await send({"type": "lifespan.startup.complete"}) + elif message["type"] == "lifespan.shutdown": + await send({"type": "lifespan.shutdown.complete"}) + return + + if scope["type"] != "http": + return + + # Read body + body = b"" + while True: + message = await receive() + body += message.get("body", b"") + if not message.get("more_body", False): + break + + # Build response + method = scope["method"] + path = scope["path"] + query = scope.get("query_string", b"").decode("utf-8") + + response_body = f"Method: {method}\nPath: {path}\nQuery: {query}\nBody: {body.decode('utf-8')}\n" + response_bytes = response_body.encode("utf-8") + + await send({ + "type": "http.response.start", + "status": 200, + "headers": [ + [b"content-type", b"text/plain"], + [b"content-length", str(len(response_bytes)).encode()], + ], + }) + await send({ + "type": "http.response.body", + "body": response_bytes, + }) diff --git a/tests/docker/asgi/docker-compose.yml b/tests/docker/asgi/docker-compose.yml new file mode 100644 index 00000000..9f1af22a --- /dev/null +++ b/tests/docker/asgi/docker-compose.yml @@ -0,0 +1,14 @@ +services: + gunicorn: + build: + context: ../../.. + dockerfile: tests/docker/asgi/Dockerfile + command: > + gunicorn + --worker-class asgi + --bind 0.0.0.0:8000 + --workers 1 + --log-level debug + app:app + ports: + - "8080:8000" diff --git a/tests/docker/asgi/test_asgi.sh b/tests/docker/asgi/test_asgi.sh new file mode 100755 index 00000000..41eccff5 --- /dev/null +++ b/tests/docker/asgi/test_asgi.sh @@ -0,0 +1,116 @@ +#!/bin/bash +# Integration test for ASGI HTTP protocol support +# +# This script tests that gunicorn's ASGI worker correctly handles +# HTTP requests directly (without uWSGI protocol). + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# Use IPv4 explicitly to avoid Docker IPv6 issues +BASE_URL="http://127.0.0.1:8080" + +cleanup() { + echo "Cleaning up..." + docker compose down -v 2>/dev/null || true +} + +trap cleanup EXIT + +echo "=== Building and starting containers ===" +docker compose up -d --build + +echo "=== Waiting for services to be ready ===" +sleep 5 + +echo "=== Running tests ===" + +# Test 1: Simple GET request +echo "Test 1: Simple GET request" +RESPONSE=$(curl -s "$BASE_URL/") +if echo "$RESPONSE" | grep -q "Method: GET"; then + echo " PASS: GET request works" +else + echo " FAIL: GET request failed" + echo " Response: $RESPONSE" + exit 1 +fi + +# Test 2: GET with query string +echo "Test 2: GET with query string" +RESPONSE=$(curl -s "$BASE_URL/search?q=test&page=1") +if echo "$RESPONSE" | grep -q "Query: q=test&page=1"; then + echo " PASS: Query string works" +else + echo " FAIL: Query string failed" + echo " Response: $RESPONSE" + exit 1 +fi + +# Test 3: POST with body +echo "Test 3: POST with body" +RESPONSE=$(curl -s -X POST -d "hello=world" "$BASE_URL/submit") +if echo "$RESPONSE" | grep -q "Method: POST" && echo "$RESPONSE" | grep -q "Body: hello=world"; then + echo " PASS: POST with body works" +else + echo " FAIL: POST with body failed" + echo " Response: $RESPONSE" + exit 1 +fi + +# Test 4: Path handling +echo "Test 4: Path handling" +RESPONSE=$(curl -s "$BASE_URL/api/v1/users") +if echo "$RESPONSE" | grep -q "Path: /api/v1/users"; then + echo " PASS: Path handling works" +else + echo " FAIL: Path handling failed" + echo " Response: $RESPONSE" + exit 1 +fi + +# Test 5: Multiple requests (keepalive) +echo "Test 5: Multiple requests (keepalive)" +for i in 1 2 3; do + RESPONSE=$(curl -s "$BASE_URL/request/$i") + if ! echo "$RESPONSE" | grep -q "Path: /request/$i"; then + echo " FAIL: Request $i failed" + exit 1 + fi +done +echo " PASS: Multiple requests work" + +# Test 6: Large POST body +echo "Test 6: Large POST body" +LARGE_BODY=$(python3 -c "print('x' * 10000)") +RESPONSE=$(curl -s -X POST -d "$LARGE_BODY" "$BASE_URL/large") +if echo "$RESPONSE" | grep -q "Method: POST" && echo "$RESPONSE" | grep -c "x" | grep -q "10000"; then + echo " PASS: Large POST body works" +else + # Verify body length in response + BODY_LINE=$(echo "$RESPONSE" | grep "Body:") + BODY_LEN=${#BODY_LINE} + if [ "$BODY_LEN" -gt 10000 ]; then + echo " PASS: Large POST body works" + else + echo " FAIL: Large POST body failed" + echo " Response length: $BODY_LEN" + exit 1 + fi +fi + +# Test 7: HTTP headers +echo "Test 7: Custom headers" +RESPONSE=$(curl -s -H "X-Custom-Header: test-value" "$BASE_URL/headers") +if echo "$RESPONSE" | grep -q "Method: GET"; then + echo " PASS: Custom headers work" +else + echo " FAIL: Custom headers failed" + echo " Response: $RESPONSE" + exit 1 +fi + +echo "" +echo "=== All tests passed! ==="