Skip to content

Commit 581b9f1

Browse files
feat: add coverage calculation
Signed-off-by: Charan Kamarapu <kamarapucharan@gmail.com>
1 parent 3b7d16a commit 581b9f1

332 files changed

Lines changed: 1098 additions & 446 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apigateway/.coveragerc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[run]
2+
branch = True
3+
parallel = True
4+
source = .
5+
data_file = /coverage/.coverage.apigateway
6+
omit =
7+
*/migrate.py
8+
*/migrations/*
9+
10+
[report]
11+
show_missing = True
12+
skip_empty = True
13+
14+
[paths]
15+
source =
16+
.
17+
/app
18+
/Users/charankamarapu/microservices/apigateway

apigateway/Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ WORKDIR /app
33
COPY requirements.txt .
44
RUN pip install --no-cache-dir -r requirements.txt
55
COPY . .
6+
RUN chmod +x /app/entrypoint.sh || true
67
ENV FLASK_RUN_HOST=0.0.0.0
78
ENV FLASK_RUN_PORT=8083
8-
CMD ["python", "app.py"]
9+
ENTRYPOINT ["/bin/sh", "/app/entrypoint.sh"]

apigateway/app.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import signal
33
from flask import Flask, request, Response, jsonify
44
import requests
5+
import coverage as _coverage
6+
57

68
app = Flask(__name__)
79

@@ -96,17 +98,20 @@ def gw_orders(subpath):
9698
return _proxy(ORDER_SERVICE_URL, f"orders/{subpath}")
9799

98100

99-
@app.route('/health', methods=['GET'])
100-
def health():
101-
return jsonify({'ok': True}), 200
102-
103101

104102
if __name__ == '__main__':
105-
# Ensure we exit quickly on SIGTERM/SIGINT during docker stop
103+
# Ensure we stop/save coverage before exiting on SIGTERM/SIGINT
106104
def _graceful_exit(signum, frame):
107-
# Let Flask/Werkzeug shutdown
108-
os._exit(0)
105+
if _coverage:
106+
try:
107+
cov = _coverage.Coverage.current()
108+
if cov is not None:
109+
cov.stop()
110+
cov.save()
111+
except Exception:
112+
pass
113+
raise SystemExit(0)
109114
signal.signal(signal.SIGTERM, _graceful_exit)
110115
signal.signal(signal.SIGINT, _graceful_exit)
111116
port = int(os.environ.get('FLASK_RUN_PORT', 8083))
112-
app.run(host='0.0.0.0', port=port)
117+
app.run(host='0.0.0.0', port=port, use_reloader=False)

apigateway/entrypoint.sh

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#!/bin/sh
2+
set -eu
3+
4+
gen_reports() {
5+
echo "[entrypoint] Generating apigateway coverage HTML..."
6+
PYTHON=python3; command -v python3 >/dev/null 2>&1 || PYTHON=python
7+
"$PYTHON" - <<'PY'
8+
import os, sys, subprocess, time
9+
data_file = os.environ.get('COVERAGE_FILE', '/coverage/.coverage.apigateway')
10+
rcfile = '.coveragerc'
11+
# Write HTML inside the service repo path if mounted
12+
html_dir = os.environ.get('HTML_DIR', '/svc_coverage/htmlcov')
13+
cov_dir = os.path.dirname(data_file) or '/coverage'
14+
prefix = os.path.basename(data_file)
15+
files = []
16+
# Poll briefly for coverage files to appear
17+
for _ in range(10):
18+
files = [os.path.join(cov_dir, f) for f in os.listdir(cov_dir) if f.startswith(prefix) and f != prefix]
19+
if files or os.path.exists(data_file):
20+
break
21+
time.sleep(0.5)
22+
combined = data_file + '.combined'
23+
try:
24+
if files:
25+
cmd = [sys.executable, '-m', 'coverage', 'combine', '--data-file', combined]
26+
cmd += files
27+
subprocess.check_call(cmd)
28+
used = combined
29+
else:
30+
used = data_file if os.path.exists(data_file) else None
31+
if not used:
32+
print('[entrypoint] No coverage data found for apigateway in', cov_dir)
33+
else:
34+
os.makedirs(html_dir, exist_ok=True)
35+
subprocess.check_call([sys.executable, '-m', 'coverage', 'report', '-m', '--data-file', used, '--rcfile', rcfile])
36+
subprocess.check_call([sys.executable, '-m', 'coverage', 'html', '-d', html_dir, '--data-file', used, '--rcfile', rcfile])
37+
print('[entrypoint] HTML written to', os.path.join(html_dir, 'index.html'))
38+
except Exception as e:
39+
print('[entrypoint] coverage combine/report/html failed:', e)
40+
PY
41+
}
42+
43+
if [ -n "${COVERAGE:-}" ]; then
44+
echo "[entrypoint] Running under coverage"
45+
export COVERAGE_FILE=${COVERAGE_FILE:-/coverage/.coverage.apigateway}
46+
python -m coverage run --rcfile=.coveragerc app.py &
47+
APP_PID=$!
48+
on_term() {
49+
echo "[entrypoint] Caught stop signal; stopping app..."
50+
kill -TERM "$APP_PID" 2>/dev/null || true
51+
wait "$APP_PID" || true
52+
# Give the app a moment to flush coverage
53+
sleep 1
54+
gen_reports || true
55+
exit 0
56+
}
57+
trap on_term INT TERM
58+
wait "$APP_PID" || true
59+
# Normal exit
60+
sleep 1
61+
gen_reports || true
62+
else
63+
python app.py
64+
fi

apigateway/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
flask==3.0.3
22
requests==2.32.3
3+
coverage==7.5.4
52 KB
Binary file not shown.
68 KB
Binary file not shown.
52 KB
Binary file not shown.
52 KB
Binary file not shown.

docker-compose.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,15 @@ services:
8585
ADMIN_EMAIL: admin@example.com
8686
ADMIN_PASSWORD: admin123
8787
RESET_ADMIN_PASSWORD: "true"
88+
COVERAGE: "1"
8889
depends_on:
8990
mysql-users:
9091
condition: service_healthy
9192
ports:
9293
- "8082:8082"
94+
volumes:
95+
- ./coverage:/coverage
96+
- ./user_service/coverage:/svc_coverage
9397

9498
product_service:
9599
build: ./product_service
@@ -102,11 +106,15 @@ services:
102106
DB_PASSWORD: password
103107
DB_NAME: product_db
104108
FLASK_RUN_PORT: "8081"
109+
COVERAGE: "1"
105110
depends_on:
106111
mysql-products:
107112
condition: service_healthy
108113
ports:
109114
- "8081:8081"
115+
volumes:
116+
- ./coverage:/coverage
117+
- ./product_service/coverage:/svc_coverage
110118

111119
order_service:
112120
build: ./order_service
@@ -126,6 +134,7 @@ services:
126134
AWS_ENDPOINT: http://localstack:4566
127135
SQS_QUEUE_URL: http://localstack:4566/000000000000/order-events
128136
FLASK_RUN_PORT: "8080"
137+
COVERAGE: "1"
129138
depends_on:
130139
mysql-orders:
131140
condition: service_healthy
@@ -137,6 +146,9 @@ services:
137146
condition: service_started
138147
ports:
139148
- "8080:8080"
149+
volumes:
150+
- ./coverage:/coverage
151+
- ./order_service/coverage:/svc_coverage
140152

141153
apigateway:
142154
build: ./apigateway
@@ -148,6 +160,7 @@ services:
148160
PRODUCT_SERVICE_URL: http://product_service:8081/api/v1
149161
ORDER_SERVICE_URL: http://order_service:8080/api/v1
150162
FLASK_RUN_PORT: "8083"
163+
COVERAGE: "1"
151164
depends_on:
152165
user_service:
153166
condition: service_started
@@ -157,3 +170,6 @@ services:
157170
condition: service_started
158171
ports:
159172
- "8083:8083"
173+
volumes:
174+
- ./coverage:/coverage
175+
- ./apigateway/coverage:/svc_coverage

0 commit comments

Comments
 (0)