77 pull_request :
88 branches : [ main ]
99
10+ concurrency :
11+ group : ${{ github.workflow }}-${{ github.ref }}
12+ cancel-in-progress : true
13+
14+ defaults :
15+ run :
16+ shell : bash
17+
1018permissions :
1119 contents : read
1220 checks : write
@@ -15,7 +23,14 @@ permissions:
1523jobs :
1624 test :
1725 runs-on : ubuntu-latest
26+ timeout-minutes : 30
27+ env :
28+ COVERAGE_THRESHOLD : " 85"
29+ PIP_DISABLE_PIP_VERSION_CHECK : " 1"
30+ PIP_PROGRESS_BAR : " off"
31+ PYTHONUNBUFFERED : " 1"
1832 strategy :
33+ fail-fast : false
1934 matrix :
2035 python-version : [ '3.12', '3.13' , '3.14' ]
2136 steps :
3146 - name : Install dependencies
3247 run : |
3348 python -m pip install --upgrade pip
34- pip install -r requirements.txt
49+ python -m pip install -r requirements.txt
50+
51+ - name : Validate dependency metadata
52+ run : |
53+ python -m pip check
3554
3655 - name : Run pylint analysis
3756 run : |
6281 with :
6382 name : coverage-html-${{ matrix.python-version }}
6483 path : tests/python/htmlcov-${{ matrix.python-version }}/
84+ if-no-files-found : warn
6585
6686 - name : Upload JUnit test results
6787 if : always()
@@ -70,6 +90,103 @@ jobs:
7090 with :
7191 name : junit-results-${{ matrix.python-version }}
7292 path : tests/python/junit-${{ matrix.python-version }}.xml
93+ if-no-files-found : warn
94+
95+ - name : Append pylint summary to job summary
96+ if : always()
97+ run : |
98+ REPORT_JSON="tests/python/pylint/reports/latest.json"
99+ {
100+ echo "### Python ${{ matrix.python-version }} – Pylint summary"
101+ echo ""
102+ if [ -f "$REPORT_JSON" ] && command -v jq >/dev/null; then
103+ TOTAL=$(jq 'length' "$REPORT_JSON")
104+ echo "- Diagnostics: $TOTAL"
105+ echo ""
106+ jq -r '
107+ group_by(.type) |
108+ map({label: (.[0].type), count: length}) |
109+ .[] | " - \(.label): \(.count)"
110+ ' "$REPORT_JSON"
111+ else
112+ echo "Pylint summary unavailable (install jq for statistics)."
113+ fi
114+ echo ""
115+ echo "- Reports artifact: pylint-reports-${{ matrix.python-version }}"
116+ echo ""
117+ } >> "$GITHUB_STEP_SUMMARY"
118+
119+ - name : Append coverage summary to job summary
120+ if : always()
121+ run : |
122+ COVERAGE_FILE="tests/python/.coverage-${{ matrix.python-version }}"
123+ HTML_DIR="tests/python/htmlcov-${{ matrix.python-version }}"
124+ COVERAGE_TXT="$(mktemp)"
125+ if [ -f "$COVERAGE_FILE" ]; then
126+ python -m coverage report --data-file="$COVERAGE_FILE" --rcfile=tests/python/.coveragerc --skip-covered --fail-under=0 > "$COVERAGE_TXT" || true
127+ fi
128+ OVERALL=""
129+ if [ -s "$COVERAGE_TXT" ]; then
130+ OVERALL=$(tail -n 1 "$COVERAGE_TXT" | awk '{print $NF}')
131+ fi
132+ {
133+ echo "### Python ${{ matrix.python-version }} – Coverage summary"
134+ echo ""
135+ echo "- Overall: ${OVERALL:-Unavailable}"
136+ echo ""
137+ echo "- HTML coverage artifact: coverage-html-${{ matrix.python-version }}"
138+ if [ -d "$HTML_DIR" ]; then
139+ echo "- Local path: $HTML_DIR/index.html"
140+ fi
141+ echo ""
142+ } >> "$GITHUB_STEP_SUMMARY"
143+ rm -f "$COVERAGE_TXT"
144+
145+ - name : Enforce coverage threshold
146+ if : steps.pytest.outcome == 'success'
147+ env :
148+ COVERAGE_FILE : tests/python/.coverage-${{ matrix.python-version }}
149+ run : |
150+ if [ ! -f "$COVERAGE_FILE" ]; then
151+ echo "Coverage file not found; skipping threshold enforcement."
152+ exit 0
153+ fi
154+ python - <<'PY'
155+ import os
156+ import re
157+ import subprocess
158+ import sys
159+
160+ threshold = float(os.getenv("COVERAGE_THRESHOLD", "0"))
161+ coverage_file = os.environ["COVERAGE_FILE"]
162+ rcfile = "tests/python/.coveragerc"
163+
164+ result = subprocess.run([
165+ sys.executable,
166+ " -m" ,
167+ " coverage" ,
168+ " report" ,
169+ f"--data-file={coverage_file}",
170+ f"--rcfile={rcfile}",
171+ " --skip-covered" ,
172+ " --fail-under=0" ,
173+ ], capture_output=True, text=True, check=False)
174+
175+ output = result.stdout.strip().splitlines()
176+ if not output :
177+ sys.exit(0)
178+
179+ match = re.search(r"(\d+(?:\.\d+)?)%$", output[-1])
180+ if not match :
181+ sys.exit(0)
182+
183+ percent = float(match.group(1))
184+ if percent < threshold :
185+ print(f"::error ::Coverage {percent:.2f}% is below required threshold {threshold:.2f}%.")
186+ sys.exit(1)
187+
188+ print(f"Coverage {percent:.2f}% meets the threshold {threshold:.2f}%.")
189+ PY
73190
74191 - name : Publish Unit Test Results to PR
75192 if : always()
0 commit comments