Skip to content

Commit bf596cc

Browse files
committed
Add pytest workflow and update makefile for coverage
Signed-off-by: Mihai Criveti <[email protected]>
1 parent 120aabc commit bf596cc

File tree

2 files changed

+189
-39
lines changed

2 files changed

+189
-39
lines changed

.github/workflows/pytest.yml

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# ===============================================================
2+
# 🧪 PyTest & Coverage – Quality Gate
3+
# ===============================================================
4+
#
5+
# This workflow
6+
# • runs the full test-suite across three Python versions
7+
# • measures branch + line coverage (fails < 40 %)
8+
# • uploads the XML/HTML coverage reports as build artifacts
9+
# • generates / commits an SVG badge (main branch only)
10+
# • posts a concise coverage table to the job summary
11+
# • executes on every push/PR to *main* and weekly
12+
# ---------------------------------------------------------------
13+
14+
name: Tests & Coverage
15+
16+
on:
17+
push:
18+
branches: ["main"]
19+
pull_request:
20+
branches: ["main"]
21+
# schedule:
22+
# - cron: '42 3 * * 1' # Monday 03:42 UTC
23+
24+
permissions:
25+
contents: write # needed to commit the badge
26+
checks: write
27+
actions: read
28+
29+
jobs:
30+
test:
31+
name: pytest (${{ matrix.python }})
32+
runs-on: ubuntu-latest
33+
34+
strategy:
35+
fail-fast: false
36+
matrix:
37+
python: ["3.10", "3.11", "3.12"]
38+
39+
env:
40+
PYTHONUNBUFFERED: "1"
41+
PIP_DISABLE_PIP_VERSION_CHECK: "1"
42+
43+
steps:
44+
# -----------------------------------------------------------
45+
# 0️⃣ Checkout
46+
# -----------------------------------------------------------
47+
- name: ⬇️ Checkout code
48+
uses: actions/checkout@v4
49+
with:
50+
fetch-depth: 1
51+
52+
# -----------------------------------------------------------
53+
# 1️⃣ Set-up Python
54+
# -----------------------------------------------------------
55+
- name: 🐍 Setup Python ${{ matrix.python }}
56+
uses: actions/setup-python@v5
57+
with:
58+
python-version: ${{ matrix.python }}
59+
cache: "pip"
60+
61+
# -----------------------------------------------------------
62+
# 2️⃣ Install dependencies
63+
# -----------------------------------------------------------
64+
- name: 📦 Install requirements
65+
run: |
66+
python -m pip install --upgrade pip
67+
if [ -f requirements-dev.txt ]; then
68+
pip install -r requirements-dev.txt
69+
else
70+
pip install pytest pytest-cov pytest-asyncio coverage[toml]
71+
fi
72+
73+
# -----------------------------------------------------------
74+
# 3️⃣ Run the tests with coverage
75+
# -----------------------------------------------------------
76+
- name: 🧪 Run pytest
77+
run: |
78+
pytest \
79+
--cov=mcpgateway \
80+
--cov-report=xml \
81+
--cov-report=html \
82+
--cov-report=term \
83+
--cov-branch \
84+
--cov-fail-under=40
85+
86+
# -----------------------------------------------------------
87+
# 4️⃣ Upload coverage artifacts (XML + HTML)
88+
# -----------------------------------------------------------
89+
90+
# - name: 📤 Upload coverage.xml
91+
# uses: actions/upload-artifact@v4
92+
# with:
93+
# name: coverage-xml-${{ matrix.python }}
94+
# path: coverage.xml
95+
#
96+
# - name: 📤 Upload HTML coverage
97+
# uses: actions/upload-artifact@v4
98+
# with:
99+
# name: htmlcov-${{ matrix.python }}
100+
# path: htmlcov/
101+
102+
# -----------------------------------------------------------
103+
# 5️⃣ Generate + commit badge (once, on main / highest ver)
104+
# -----------------------------------------------------------
105+
# - name: 📊 Create coverage badge
106+
# if: matrix.python == '3.11' && github.ref == 'refs/heads/main'
107+
# id: make_badge
108+
# uses: tj-actions/coverage-badge@v2
109+
# with:
110+
# coverage-file: coverage.xml # input
111+
# output: .github/badges/coverage.svg # output file
112+
# env:
113+
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
114+
115+
# - name: 🚀 Commit badge
116+
# if: steps.make_badge.outputs.badge-updated == 'true'
117+
# uses: stefanzweifel/git-auto-commit-action@v5
118+
# with:
119+
# commit_message: "docs(badge): update coverage badge"
120+
# file_pattern: ".github/badges/coverage.svg"
121+
122+
# -----------------------------------------------------------
123+
# 6️⃣ Publish coverage table to the job summary
124+
# -----------------------------------------------------------
125+
# - name: 📝 Coverage summary
126+
# if: always()
127+
# run: |
128+
# echo "### Coverage – Python ${{ matrix.python }}" >> $GITHUB_STEP_SUMMARY
129+
# # table header
130+
# echo "| File | Stmts | Miss | Branch | BrMiss | Cover |" >> $GITHUB_STEP_SUMMARY
131+
# echo "|------|------:|-----:|-------:|-------:|------:|" >> $GITHUB_STEP_SUMMARY
132+
# # append trimmed coverage report
133+
# coverage json -q -o cov.json
134+
# python - <<'PY'
135+
# import json, os, pathlib, sys
136+
# d = json.load(open("cov.json"))
137+
# for f in d["files"].values():
138+
# rel = pathlib.Path(f["filename"]).relative_to(pathlib.Path().resolve())
139+
# print(f"| {rel} | {f['summary']['num_statements']} | {f['summary']['missing_lines']} | "
140+
# f"{f['summary']['num_branches']} | {f['summary']['missing_branches']} | "
141+
# f"{f['summary']['percent_covered']:.1f}% |")
142+
# PY >> $GITHUB_STEP_SUMMARY

Makefile

Lines changed: 47 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -117,20 +117,16 @@ check-env:
117117

118118

119119
# =============================================================================
120-
# ▶️ SERVE & TESTING
120+
# ▶️ SERVE
121121
# =============================================================================
122-
# help: ▶️ SERVE & TESTING
122+
# help: ▶️ SERVE
123123
# help: serve - Run production Gunicorn server on :4444
124124
# help: certs - Generate self-signed TLS cert & key in ./certs (won't overwrite)
125125
# help: serve-ssl - Run Gunicorn behind HTTPS on :4444 (uses ./certs)
126126
# help: dev - Run fast-reload dev server (uvicorn)
127127
# help: run - Execute helper script ./run.sh
128-
# help: smoketest - Run smoketest.py --verbose (build container, add MCP server, test endpoints)
129-
# help: test - Run unit tests with pytest
130-
# help: test-curl - Smoke-test API endpoints with curl script
131-
# help: pytest-examples - Run README / examples through pytest-examples
132128

133-
.PHONY: serve serve-ssl dev run test test-curl pytest-examples certs clean
129+
.PHONY: serve serve-ssl dev run certs
134130

135131
## --- Primary servers ---------------------------------------------------------
136132
serve:
@@ -159,25 +155,6 @@ certs: ## Generate ./certs/cert.pem & ./certs/key.pem
159155
fi
160156
chmod 640 certs/key.pem
161157

162-
## --- Testing -----------------------------------------------------------------
163-
smoketest:
164-
@echo "🚀 Running smoketest…"
165-
@./smoketest.py --verbose || { echo "❌ Smoketest failed!"; exit 1; }
166-
@echo "✅ Smoketest passed!"
167-
168-
test:
169-
@echo "🧪 Running tests..."
170-
@test -d "$(VENV_DIR)" || make venv
171-
@/bin/bash -c "source $(VENV_DIR)/bin/activate && python3 -m pip install pytest pytest-asyncio pytest-cov -q && python3 -m pytest --maxfail=0 --disable-warnings -v"
172-
173-
pytest-examples:
174-
@echo "🧪 Testing README examples..."
175-
@test -d "$(VENV_DIR)" || make venv
176-
@/bin/bash -c "source $(VENV_DIR)/bin/activate && python3 -m pip install pytest pytest-examples -q && pytest -v test_readme.py"
177-
178-
test-curl:
179-
./test_endpoints.sh
180-
181158
## --- House-keeping -----------------------------------------------------------
182159
# help: clean - Remove caches, build artefacts, virtualenv, docs, certs, coverage, SBOM, etc.
183160
.PHONY: clean
@@ -195,16 +172,32 @@ clean:
195172

196173

197174
# =============================================================================
198-
# 📊 COVERAGE & METRICS
175+
# 🧪 TESTING
199176
# =============================================================================
200-
# help: 📊 COVERAGE & METRICS
177+
# help: 🧪 TESTING
178+
# help: smoketest - Run smoketest.py --verbose (build container, add MCP server, test endpoints)
179+
# help: test - Run unit tests with pytest
201180
# help: coverage - Run tests with coverage, emit md/HTML/XML + badge
202-
# help: pip-licenses - Produce dependency license inventory (markdown)
203-
# help: scc - Quick LoC/complexity snapshot with scc
204-
# help: scc-report - Generate HTML LoC & per-file metrics with scc
205-
.PHONY: coverage pip-licenses scc scc-report
181+
# help: test-curl - Smoke-test API endpoints with curl script
182+
# help: pytest-examples - Run README / examples through pytest-examples
183+
184+
.PHONY: smoketest test coverage pytest-examples test-curl
185+
186+
## --- Automated checks --------------------------------------------------------
187+
smoketest:
188+
@echo "🚀 Running smoketest…"
189+
@./smoketest.py --verbose || { echo "❌ Smoketest failed!"; exit 1; }
190+
@echo "✅ Smoketest passed!"
191+
192+
test:
193+
@echo "🧪 Running tests…"
194+
@test -d "$(VENV_DIR)" || $(MAKE) venv
195+
@/bin/bash -c "source $(VENV_DIR)/bin/activate && \
196+
python3 -m pip install -q pytest pytest-asyncio pytest-cov && \
197+
python3 -m pytest --maxfail=0 --disable-warnings -v"
206198

207199
coverage:
200+
@test -d "$(VENV_DIR)" || $(MAKE) venv
208201
@mkdir -p $(TEST_DOCS_DIR)
209202
@printf "# Unit tests\n\n" > $(DOCS_DIR)/docs/test/unittest.md
210203
@/bin/bash -c "source $(VENV_DIR)/bin/activate && \
@@ -217,13 +210,30 @@ coverage:
217210
@/bin/bash -c "source $(VENV_DIR)/bin/activate && \
218211
coverage report --format=markdown -m --no-skip-covered \
219212
>> $(DOCS_DIR)/docs/test/unittest.md"
220-
@/bin/bash -c "source $(VENV_DIR)/bin/activate && \
221-
coverage html -d $(COVERAGE_DIR) --include=app/*"
213+
@/bin/bash -c "source $(VENV_DIR)/bin/activate && coverage html -d $(COVERAGE_DIR) --include=app/*"
222214
@/bin/bash -c "source $(VENV_DIR)/bin/activate && coverage xml"
223-
@/bin/bash -c "source $(VENV_DIR)/bin/activate && \
224-
coverage-badge -fo $(DOCS_DIR)/docs/images/coverage.svg"
215+
@/bin/bash -c "source $(VENV_DIR)/bin/activate && coverage-badge -fo $(DOCS_DIR)/docs/images/coverage.svg"
225216
@echo "✅ Coverage artefacts: md, HTML in $(COVERAGE_DIR), XML & badge ✔"
226217

218+
pytest-examples:
219+
@echo "🧪 Testing README examples…"
220+
@test -d "$(VENV_DIR)" || $(MAKE) venv
221+
@/bin/bash -c "source $(VENV_DIR)/bin/activate && \
222+
python3 -m pip install -q pytest pytest-examples && \
223+
pytest -v test_readme.py"
224+
225+
test-curl:
226+
./test_endpoints.sh
227+
228+
# =============================================================================
229+
# 📊 METRICS
230+
# =============================================================================
231+
# help: 📊 METRICS
232+
# help: pip-licenses - Produce dependency license inventory (markdown)
233+
# help: scc - Quick LoC/complexity snapshot with scc
234+
# help: scc-report - Generate HTML LoC & per-file metrics with scc
235+
.PHONY: pip-licenses scc scc-report
236+
227237
pip-licenses:
228238
@/bin/bash -c "source $(VENV_DIR)/bin/activate && python3 -m uv pip install pip-licenses"
229239
@mkdir -p $(dir $(LICENSES_MD))
@@ -700,9 +710,7 @@ dockle:
700710

701711
# help: hadolint - Lint Containerfile/Dockerfile(s) with hadolint
702712
.PHONY: hadolint
703-
HADOFILES := Containerfile Dockerfile Dockerfile.*
704-
705-
# Which files to check (edit as you like)
713+
# List of Containerfile/Dockerfile patterns to scan
706714
HADOFILES := Containerfile Containerfile.* Dockerfile Dockerfile.*
707715

708716
hadolint:

0 commit comments

Comments
 (0)