Skip to content

Commit 568b0bb

Browse files
fix(doccano-django): own skip-bootstrap replay mode
Signed-off-by: Akash Kumar <meakash7902@gmail.com>
1 parent 041050c commit 568b0bb

5 files changed

Lines changed: 69 additions & 24 deletions

File tree

doccano-django/Dockerfile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,10 @@
1212
# (PR #1889) and pipeline 3572 (PR #1964 minimal repro) where the
1313
# bug originally manifested.
1414
FROM doccano/doccano:backend
15+
16+
USER root
17+
COPY doccano-entrypoint.sh /opt/bin/doccano-keploy-entrypoint.sh
18+
RUN chmod +x /opt/bin/doccano-keploy-entrypoint.sh
19+
USER doccano
20+
21+
ENTRYPOINT ["/opt/bin/doccano-keploy-entrypoint.sh"]

doccano-django/README.md

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,11 @@ that:
3030
## What's in here
3131

3232
* `Dockerfile` — thin wrapper around `doccano/doccano:backend` pinning
33-
the upstream version this sample tracks. Future doccano releases
34-
that change the bug-triggering shape are addressed by retagging
35-
here, not by scattering version pins across the lane scripts in
36-
`keploy/integrations` / `keploy/enterprise`.
33+
the upstream version this sample tracks and installing the
34+
sample entrypoint that honors `DOCCANO_SKIP_BOOTSTRAP=1`. Future
35+
doccano releases that change the bug-triggering shape are addressed
36+
by retagging here, not by scattering version pins across the lane
37+
scripts in `keploy/integrations` / `keploy/enterprise`.
3738
* `docker-compose.yml` — the orchestration: postgres-13 alongside
3839
the doccano backend, on a fixed subnet so the lane scripts can
3940
rely on stable IPs across record/replay phases.
@@ -55,13 +56,20 @@ that:
5556
```sh
5657
docker compose up -d
5758
./flow.sh bootstrap
59+
60+
docker compose down
61+
DOCCANO_SKIP_BOOTSTRAP=1 docker compose up -d
5862
./flow.sh record-traffic
63+
5964
docker compose down -v
6065
```
6166

6267
This is what the keploy/integrations and keploy/enterprise CI
6368
lanes wrap in `keploy record` / `keploy test` — the base compose
64-
is uninstrumented and runs unchanged inside those lanes.
69+
is uninstrumented and runs unchanged inside those lanes. The first
70+
launch runs doccano's migrations and creates the admin user; the
71+
second launch skips bootstrap and starts gunicorn directly against
72+
the populated database volume.
6573

6674
### Without keploy — measuring real Python line coverage
6775

@@ -72,10 +80,14 @@ add `coverage.py` per-worker tracking:
7280
mkdir -p coverage
7381
docker compose -f docker-compose.yml -f docker-compose.coverage.yml up -d --build
7482
./flow.sh bootstrap
83+
84+
docker compose -f docker-compose.yml -f docker-compose.coverage.yml down
85+
DOCCANO_SKIP_BOOTSTRAP=1 docker compose -f docker-compose.yml -f docker-compose.coverage.yml up -d --build
7586
./flow.sh record-traffic
87+
7688
docker compose -f docker-compose.yml -f docker-compose.coverage.yml kill -s SIGTERM backend
7789
sleep 3
78-
docker compose -f docker-compose.yml -f docker-compose.coverage.yml up -d backend
90+
DOCCANO_SKIP_BOOTSTRAP=1 docker compose -f docker-compose.yml -f docker-compose.coverage.yml up -d backend
7991
./flow.sh coverage
8092
docker compose -f docker-compose.yml -f docker-compose.coverage.yml down -v
8193
```
@@ -91,17 +103,19 @@ ignore it and run the base compose, paying zero coverage cost.
91103
```sh
92104
docker compose up -d
93105
./flow.sh bootstrap
106+
docker compose down
94107

95108
# In one shell:
96-
keploy record -c "docker compose up" --container-name doccano_backend \
109+
keploy record -c "DOCCANO_SKIP_BOOTSTRAP=1 docker compose up" --container-name doccano_backend \
97110
--proxy-port 18081 --dns-port 18082
98111

99112
# In another shell:
100113
./flow.sh record-traffic
101114
# SIGINT keploy when traffic returns
102115

103-
keploy test -c "docker compose up" --containerName doccano_backend \
104-
--apiTimeout 60 --delay 20 --proxy-port 18081 --dns-port 18082
116+
keploy test -c "DOCCANO_SKIP_BOOTSTRAP=1 docker compose up" --containerName doccano_backend \
117+
--apiTimeout 60 --delay 20 --host 127.0.0.1 --port 18080 \
118+
--proxy-port 18081 --dns-port 18082
105119
```
106120

107121
Expected outcome with the integrations fix in place: 0 failures,
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/env bash
2+
set -Eeuo pipefail
3+
4+
if [ "${DOCCANO_SKIP_BOOTSTRAP:-0}" = "1" ]; then
5+
echo "Making staticfiles"
6+
if [ ! -d staticfiles ] || ! find staticfiles -mindepth 1 -print -quit | grep -q .; then
7+
echo "Executing collectstatic"
8+
python manage.py collectstatic --noinput
9+
fi
10+
11+
echo "Starting django without bootstrap"
12+
exec gunicorn \
13+
"--bind=${HOST:-0.0.0.0}:${PORT:-8000}" \
14+
"--workers=${WORKERS:-1}" \
15+
--timeout=300 \
16+
--capture-output \
17+
--log-level info \
18+
config.wsgi
19+
fi
20+
21+
exec /opt/bin/prod-django.sh "$@"

doccano-django/docker-compose.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
# DB state persists.
1212
# 2. DOCCANO_SKIP_BOOTSTRAP=1 → backend re-launches in
1313
# gunicorn-only mode against the populated volume; recording /
14-
# replay run against this incarnation.
14+
# replay run against this incarnation. The sample defaults to one
15+
# worker to keep Django's per-process ContentType cache
16+
# deterministic across keploy replay.
1517
#
1618
# The split is what gives the lane a deterministic DB starting state
1719
# without paying the migration cost on every record/replay invocation.
@@ -39,6 +41,7 @@ services:
3941
DEBUG: "False"
4042
DJANGO_SETTINGS_MODULE: config.settings.production
4143
DOCCANO_SKIP_BOOTSTRAP: "${DOCCANO_SKIP_BOOTSTRAP:-0}"
44+
WORKERS: "${DOCCANO_WORKERS:-1}"
4245
depends_on:
4346
postgres:
4447
condition: service_healthy

doccano-django/flow.sh

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -144,21 +144,21 @@ doccano_record_traffic() {
144144
# the recording captures nothing.
145145
doccano_wait_for_fixed_token 240 >/dev/null
146146

147-
# Worker-cache warmup. doccano runs 4 gunicorn workers; each
148-
# worker keeps its own per-process Django ContentType cache and
149-
# populates it lazily on the first polymorphic-resolver query
150-
# that worker handles. Recording lanes that terminate with
151-
# SIGINT (rather than waiting on a long --record-timer) need
152-
# every worker's cache warmed before the explicit test traffic
153-
# fires — otherwise cold workers fire their own
154-
# django_content_type lookups at replay-time, find empty perTest
155-
# cohorts, and the dependent endpoints return HTTP 500.
147+
# Worker-cache warmup. The sample defaults to one gunicorn worker
148+
# for deterministic keploy replay, but DOCCANO_WORKERS can raise
149+
# that count for local experiments. Each worker keeps its own
150+
# per-process Django ContentType cache and populates it lazily on
151+
# the first polymorphic-resolver query that worker handles.
152+
# Recording lanes that terminate with SIGINT (rather than waiting
153+
# on a long --record-timer) need every worker's cache warmed before
154+
# the explicit test traffic fires — otherwise cold workers fire
155+
# their own django_content_type lookups at replay-time, find empty
156+
# perTest cohorts, and the dependent endpoints return HTTP 500.
156157
#
157-
# 4 workers × 4 requests = 16 calls is gunicorn-dispatch-jitter
158-
# safe; /v1/me is the cheapest authenticated endpoint and
159-
# exercises the same auth-token + ContentType chain as real
160-
# test calls, so each iteration cleanly warms one worker's
161-
# cache.
158+
# The fixed 16 calls are gunicorn-dispatch-jitter safe for the
159+
# previous 4-worker default; /v1/me is the cheapest authenticated
160+
# endpoint and exercises the same auth-token + ContentType chain
161+
# as real test calls.
162162
local warm_idx
163163
for warm_idx in $(seq 1 16); do
164164
curl -sS -H "$h_token" "$base/v1/me" >/dev/null 2>&1 || true

0 commit comments

Comments
 (0)