Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add additional test cases for process management #88

Merged
merged 11 commits into from
Sep 13, 2024
Merged
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ jobs:
run: just lint
- name: Run tests
run: just test
- name: Run fast tests
run: just test-fast

test-postgres:
runs-on: ubuntu-latest
Expand Down
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ cover/
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
*.sqlite3
*.sqlite3-journal

# Flask stuff:
instance/
Expand Down
6 changes: 6 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,9 @@ just test-sqlite
# To run all of the above:
just test-dbs
```

Due to database worker process' tests, tests cannot run using an in-memory database, which means tests run quite slow locally. If you're not modifying the worker, and want you tests run run quicker, run:

```sh
just test-fast
```
27 changes: 19 additions & 8 deletions django_tasks/backends/database/management/commands/db_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,28 @@

class Worker:
def __init__(
self, *, queue_names: List[str], interval: float, batch: bool, backend_name: str
self,
*,
queue_names: List[str],
interval: float,
batch: bool,
backend_name: str,
startup_delay: bool,
):
self.queue_names = queue_names
self.process_all_queues = "*" in queue_names
self.interval = interval
self.batch = batch
self.backend_name = backend_name
self.startup_delay = startup_delay

self.running = True
self.running_task = False

def shutdown(self, signum: int, frame: Optional[FrameType]) -> None:
if not self.running:
logger.warning(
"Received %s - shutting down immediately.", signal.strsignal(signum)
"Received %s - terminating current task.", signal.strsignal(signum)
)
sys.exit(1)

Expand All @@ -67,7 +74,7 @@ def start(self) -> None:

logger.info("Starting worker for queues=%s", ",".join(self.queue_names))

if self.interval:
if self.startup_delay and self.interval:
# Add a random small delay before starting the loop to avoid a thundering herd
time.sleep(random.random())

Expand All @@ -87,7 +94,7 @@ def start(self) -> None:
except OperationalError as e:
# Ignore locked databases and keep trying.
# It should unlock eventually.
if "database is locked" in e.args[0]:
if "is locked" in e.args[0]:
task_result = None
else:
raise
Expand Down Expand Up @@ -150,10 +157,6 @@ def run_task(self, db_task_result: DBTaskResult) -> None:
task_result=task_result,
)

# If the user tried to terminate, let them
if isinstance(e, KeyboardInterrupt):
raise


def valid_backend_name(val: str) -> str:
try:
Expand Down Expand Up @@ -205,6 +208,12 @@ def add_arguments(self, parser: ArgumentParser) -> None:
dest="backend_name",
help="The backend to operate on (default: %(default)r)",
)
parser.add_argument(
"--no-startup-delay",
action="store_false",
dest="startup_delay",
help="Don't add a small delay at startup.",
)

def configure_logging(self, verbosity: int) -> None:
if verbosity == 0:
Expand All @@ -229,6 +238,7 @@ def handle(
interval: float,
batch: bool,
backend_name: str,
startup_delay: bool,
**options: dict,
) -> None:
self.configure_logging(verbosity)
Expand All @@ -238,6 +248,7 @@ def handle(
interval=interval,
batch=batch,
backend_name=backend_name,
startup_delay=startup_delay,
)

worker.start()
Expand Down
4 changes: 3 additions & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ test *ARGS:
python -m coverage report
python -m coverage html

test-fast *ARGS:
python -m manage test --shuffle --noinput --settings tests.settings_fast {{ ARGS }}

format:
python -m ruff check django_tasks tests --fix
python -m ruff format django_tasks tests
Expand All @@ -19,7 +22,6 @@ lint:
python -m mypy django_tasks tests

start-dbs:
docker-compose pull
docker-compose up -d

test-sqlite *ARGS:
Expand Down
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ postgres = [
select = ["E", "F", "I", "W", "N", "B", "A", "C4", "T20", "DJ"]
ignore = ["E501", "DJ008"]

[tool.ruff.lint.per-file-ignores]
"tests/db_worker_test_settings.py" = ["F403", "F405"]
"tests/settings_fast.py" = ["F403", "F405"]

[tool.mypy]
plugins = ["mypy_django_plugin.main"]
warn_unused_ignores = true
Expand Down
9 changes: 9 additions & 0 deletions tests/db_worker_test_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from .settings import *

TASKS = {"default": {"BACKEND": "django_tasks.backends.database.DatabaseBackend"}}

# Force the test DB to be used
if "sqlite" in DATABASES["default"]["ENGINE"]:
DATABASES["default"]["NAME"] = DATABASES["default"]["TEST"]["NAME"]
else:
DATABASES["default"]["NAME"] = "test_" + DATABASES["default"]["NAME"]
8 changes: 5 additions & 3 deletions tests/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,18 @@

DATABASES = {
"default": dj_database_url.config(
default="sqlite://:memory:"
if IN_TEST
else "sqlite:///" + os.path.join(BASE_DIR, "db.sqlite3")
default="sqlite:///" + os.path.join(BASE_DIR, "db.sqlite3")
)
}

# Set exclusive transactions in 5.1+
if django.VERSION >= (5, 1) and "sqlite" in DATABASES["default"]["ENGINE"]:
DATABASES["default"].setdefault("OPTIONS", {})["transaction_mode"] = "EXCLUSIVE"

if "sqlite" in DATABASES["default"]["ENGINE"]:
DATABASES["default"]["TEST"] = {"NAME": os.path.join(BASE_DIR, "db-test.sqlite3")}


USE_TZ = True

if not IN_TEST:
Expand Down
5 changes: 5 additions & 0 deletions tests/settings_fast.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .settings import *

# Unset custom test settings to use in-memory DB
if "sqlite" in DATABASES["default"]["ENGINE"]:
del DATABASES["default"]["TEST"]
15 changes: 15 additions & 0 deletions tests/tasks.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import time

from django_tasks import task


Expand Down Expand Up @@ -54,3 +56,16 @@ def enqueue_on_commit_task() -> None:
@task(enqueue_on_commit=False)
def never_enqueue_on_commit_task() -> None:
pass


@task()
def hang() -> None:
"""
Do nothing for 5 minutes
"""
time.sleep(300)


@task()
def sleep_for(seconds: float) -> None:
time.sleep(seconds)
Loading
Loading