Skip to content

Commit 3d1606b

Browse files
committed
Add coverage check workflow and update dependencies
1 parent c6f4a27 commit 3d1606b

File tree

3 files changed

+335
-1
lines changed

3 files changed

+335
-1
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
name: Code Coverage
2+
3+
permissions:
4+
contents: read
5+
6+
on:
7+
pull_request:
8+
9+
jobs:
10+
coverage:
11+
runs-on: ubuntu-latest
12+
steps:
13+
#----------------------------------------------
14+
# check-out repo and set-up python
15+
#----------------------------------------------
16+
- name: Check out repository
17+
uses: actions/checkout@v4
18+
with:
19+
fetch-depth: 0 # Needed for coverage comparison
20+
ref: ${{ github.event.pull_request.head.ref || github.ref_name }}
21+
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
22+
- name: Set up python
23+
id: setup-python
24+
uses: actions/setup-python@v5
25+
with:
26+
python-version: "3.10"
27+
#----------------------------------------------
28+
# ----- install & configure poetry -----
29+
#----------------------------------------------
30+
- name: Install Poetry
31+
uses: snok/install-poetry@v1
32+
with:
33+
virtualenvs-create: true
34+
virtualenvs-in-project: true
35+
installer-parallel: true
36+
37+
#----------------------------------------------
38+
# load cached venv if cache exists
39+
#----------------------------------------------
40+
- name: Load cached venv
41+
id: cached-poetry-dependencies
42+
uses: actions/cache@v4
43+
with:
44+
path: .venv
45+
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ github.event.repository.name }}-${{ hashFiles('**/poetry.lock') }}
46+
#----------------------------------------------
47+
# install dependencies if cache does not exist
48+
#----------------------------------------------
49+
- name: Install dependencies
50+
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
51+
run: poetry install --no-interaction --no-root
52+
#----------------------------------------------
53+
# install your root project, if required
54+
#----------------------------------------------
55+
- name: Install library
56+
run: poetry install --no-interaction
57+
#----------------------------------------------
58+
# run test suite
59+
#----------------------------------------------
60+
- name: Run tests with coverage
61+
run: poetry run python -m pytest tests/unit --cov=src --cov-report=xml --cov-report=term
62+
#----------------------------------------------
63+
# check for coverage override
64+
#----------------------------------------------
65+
- name: Check for coverage override
66+
id: override
67+
run: |
68+
OVERRIDE_COMMENT=$(echo "${{ github.event.pull_request.body }}" | grep -E "SKIP_COVERAGE_CHECK\s*=" || echo "")
69+
if [ -n "$OVERRIDE_COMMENT" ]; then
70+
echo "override=true" >> $GITHUB_OUTPUT
71+
REASON=$(echo "$OVERRIDE_COMMENT" | sed -E 's/.*SKIP_COVERAGE_CHECK\s*=\s*(.+)/\1/')
72+
echo "reason=$REASON" >> $GITHUB_OUTPUT
73+
echo "Coverage override found in PR description: $REASON"
74+
else
75+
echo "override=false" >> $GITHUB_OUTPUT
76+
echo "No coverage override found"
77+
fi
78+
#----------------------------------------------
79+
# check coverage percentage
80+
#----------------------------------------------
81+
- name: Check coverage percentage
82+
if: steps.override.outputs.override == 'false'
83+
run: |
84+
COVERAGE_FILE="coverage.xml"
85+
if [ ! -f "$COVERAGE_FILE" ]; then
86+
echo "ERROR: Coverage file not found at $COVERAGE_FILE"
87+
exit 1
88+
fi
89+
90+
# Install xmllint if not available
91+
if ! command -v xmllint &> /dev/null; then
92+
sudo apt-get update && sudo apt-get install -y libxml2-utils
93+
fi
94+
95+
COVERED=$(xmllint --xpath "string(//coverage/@lines-covered)" "$COVERAGE_FILE")
96+
TOTAL=$(xmllint --xpath "string(//coverage/@lines-valid)" "$COVERAGE_FILE")
97+
98+
# Use Python for floating-point math
99+
PERCENTAGE=$(python3 -c "covered=${COVERED}; total=${TOTAL}; print(round((covered/total)*100, 2))")
100+
101+
echo "Branch Coverage: ${PERCENTAGE}%"
102+
echo "Required Coverage: 85%"
103+
104+
# Use Python to compare the coverage with 85
105+
python3 -c "import sys; sys.exit(0 if float('${PERCENTAGE}') >= 85 else 1)"
106+
if [ $? -eq 1 ]; then
107+
echo "ERROR: Coverage is ${PERCENTAGE}%, which is less than the required 85%"
108+
exit 1
109+
else
110+
echo "SUCCESS: Coverage is ${PERCENTAGE}%, which meets the required 85%"
111+
fi
112+
#----------------------------------------------
113+
# coverage enforcement summary
114+
#----------------------------------------------
115+
- name: Coverage enforcement summary
116+
run: |
117+
if [ "${{ steps.override.outputs.override }}" == "true" ]; then
118+
echo "⚠️ Coverage checks bypassed: ${{ steps.override.outputs.reason }}"
119+
echo "Please ensure this override is justified and temporary"
120+
else
121+
echo "✅ Coverage checks enforced - minimum 85% required"
122+
fi

0 commit comments

Comments
 (0)