gunicorn/tests/support_dirty_app.py
Benoit Chesneau 1d0df29796 feat(dirty): add class attribute workers support and e2e tests
- Add get_app_workers_attribute() to read workers class attribute
- Update _parse_app_specs() to check class attribute when no config override
- Add Docker-based e2e tests for per-app worker allocation
- Add test apps: HeavyModelApp (workers=2), LightweightApp
- Add unit tests for get_app_workers_attribute function
- Add integration tests for class attribute detection
2026-02-01 03:04:35 +01:00

158 lines
3.7 KiB
Python

#
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.
"""Support module for dirty app tests."""
from gunicorn.dirty.app import DirtyApp
class TestDirtyApp(DirtyApp):
"""A simple dirty app for testing."""
def __init__(self):
self.initialized = False
self.closed = False
self.data = {}
def init(self):
self.initialized = True
self.data['init_called'] = True
def store(self, key, value):
self.data[key] = value
return {"stored": True, "key": key}
def retrieve(self, key):
return self.data.get(key)
def compute(self, a, b, operation="add"):
if operation == "add":
return a + b
elif operation == "multiply":
return a * b
else:
raise ValueError(f"Unknown operation: {operation}")
def close(self):
self.closed = True
self.data.clear()
class BrokenInitApp(DirtyApp):
"""A dirty app that fails during init."""
def init(self):
raise RuntimeError("Init failed!")
class BrokenInstantiationApp(DirtyApp):
"""A dirty app that fails during instantiation."""
def __init__(self):
raise RuntimeError("Cannot instantiate!")
class NotAClass:
"""Not a class, just an instance for testing."""
pass
not_a_class = NotAClass()
class MissingCallApp:
"""An invalid dirty app missing __call__."""
def init(self):
pass
def close(self):
pass
class SlowDirtyApp(DirtyApp):
"""A dirty app with slow methods for timeout testing."""
def __init__(self):
self.initialized = False
self.closed = False
def init(self):
self.initialized = True
def slow_action(self, delay=1.0):
"""An action that takes time to complete."""
import time
time.sleep(delay)
return {"delayed": True, "duration": delay}
def fast_action(self):
"""A fast action for comparison."""
return {"fast": True}
def close(self):
self.closed = True
class HeavyModelApp(DirtyApp):
"""A dirty app that simulates a heavy model requiring limited workers.
Uses the workers class attribute to limit how many workers load this app.
"""
workers = 2 # Only 2 workers should load this app
def __init__(self):
self.initialized = False
self.closed = False
self.model_data = None
self.worker_id = None
def init(self):
import os
self.initialized = True
# Store the worker PID to verify which worker handled the request
self.worker_id = os.getpid()
# Simulate loading a heavy model
self.model_data = {"loaded": True, "worker": self.worker_id}
def predict(self, data):
"""Simulate model prediction."""
return {
"prediction": f"result_for_{data}",
"worker_id": self.worker_id,
}
def get_worker_id(self):
"""Return the worker ID that loaded this app."""
return self.worker_id
def close(self):
self.closed = True
self.model_data = None
class LightweightApp(DirtyApp):
"""A lightweight app that should load on all workers."""
def __init__(self):
self.initialized = False
self.closed = False
self.worker_id = None
def init(self):
import os
self.initialized = True
self.worker_id = os.getpid()
def ping(self):
"""Simple ping action."""
return {"pong": True, "worker_id": self.worker_id}
def get_worker_id(self):
"""Return the worker ID that loaded this app."""
return self.worker_id
def close(self):
self.closed = True