Skip to content

Commit e91a393

Browse files
Add pylint, pip caching improvements, and static analysis to GitHub workflow (#106)
1 parent 2f9c577 commit e91a393

File tree

1 file changed

+95
-10
lines changed

1 file changed

+95
-10
lines changed

.github/workflows/python-tests.yml

Lines changed: 95 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,52 +21,137 @@ jobs:
2121
steps:
2222
- uses: actions/checkout@v4
2323

24+
# Load dependencies from GitHub runner cache for quicker install
2425
- name: Set up Python ${{ matrix.python-version }}
2526
uses: actions/setup-python@v5
2627
with:
2728
python-version: ${{ matrix.python-version }}
29+
cache: pip
30+
cache-dependency-path: requirements.txt
2831

2932
- name: Install dependencies
3033
run: |
3134
python -m pip install --upgrade pip
3235
pip install -r requirements.txt
3336
34-
- name: Install pytest and pytest-cov
37+
# Lint the Python files & upload the result statistics
38+
- name: Run pylint analysis
39+
id: pylint
40+
continue-on-error: true
41+
run: |
42+
mkdir -p tests/python/pylint/reports
43+
# Use python -m pylint and tee to ensure output is captured and visible in logs
44+
PYTHONPATH=$(pwd) python -m pylint --rcfile tests/python/.pylintrc infrastructure samples setup shared tests 2>&1 | tee tests/python/pylint/reports/latest.txt
45+
46+
- name: Upload pylint reports
47+
if: always()
48+
uses: actions/upload-artifact@v4
49+
with:
50+
name: pylint-reports-${{ matrix.python-version }}
51+
path: tests/python/pylint/reports/
52+
53+
# Static code analysis through simple compilation to ensure code is syntactically sound
54+
- name: Verify bytecode compilation
3555
run: |
36-
pip install pytest pytest-cov
56+
python -m compileall infrastructure samples setup shared tests
3757
3858
# Run tests with continue-on-error so that coverage and PR comments are always published.
3959
# The final step will explicitly fail the job if any test failed, ensuring PRs cannot be merged with failing tests.
4060
- name: Run pytest with coverage and generate JUnit XML
61+
id: pytest
4162
run: |
42-
PYTHONPATH=$(pwd) COVERAGE_FILE=tests/python/.coverage-${{ matrix.python-version }} pytest --cov=shared/python --cov-config=tests/python/.coveragerc --cov-report=html:tests/python/htmlcov-${{ matrix.python-version }} --junitxml=tests/python/junit-${{ matrix.python-version }}.xml tests/python/
63+
PYTHONPATH=$(pwd) COVERAGE_FILE=tests/python/.coverage-${{ matrix.python-version }} pytest --cov=shared/python --cov-config=tests/python/.coveragerc --cov-report=html:tests/python/htmlcov-${{ matrix.python-version }} --cov-report=term-missing --junitxml=tests/python/junit-${{ matrix.python-version }}.xml tests/python/
4364
continue-on-error: true
4465

4566
- name: Upload coverage HTML report
67+
if: always()
4668
uses: actions/upload-artifact@v4
4769
with:
4870
name: coverage-html-${{ matrix.python-version }}
4971
path: tests/python/htmlcov-${{ matrix.python-version }}/
5072

5173
- name: Upload JUnit test results
74+
if: always()
5275
uses: actions/upload-artifact@v4
76+
continue-on-error: true
5377
with:
5478
name: junit-results-${{ matrix.python-version }}
5579
path: tests/python/junit-${{ matrix.python-version }}.xml
5680

57-
- name: Publish Unit Test Results to PR
81+
# Extract all linting and coverage results in preparation for publish
82+
- name: Extract and Summarize Metrics
83+
if: always()
84+
id: metrics
85+
run: |
86+
# Pylint Score
87+
TEXT_REPORT="tests/python/pylint/reports/latest.txt"
88+
if [ -s "$TEXT_REPORT" ]; then
89+
PYLINT_SCORE=$(grep -Eo 'Your code has been rated at [0-9.]+/10' "$TEXT_REPORT" | grep -Eo '[0-9.]+/10' | head -n 1)
90+
if [ -n "$PYLINT_SCORE" ]; then
91+
echo "pylint_score=$PYLINT_SCORE" >> "$GITHUB_OUTPUT"
92+
else
93+
echo "pylint_score=N/A" >> "$GITHUB_OUTPUT"
94+
fi
95+
else
96+
echo "pylint_score=N/A" >> "$GITHUB_OUTPUT"
97+
fi
98+
99+
# Coverage Percentage
100+
if [ -f "tests/python/.coverage-${{ matrix.python-version }}" ]; then
101+
TOTAL_COV=$(PYTHONPATH=$(pwd) COVERAGE_FILE=tests/python/.coverage-${{ matrix.python-version }} python -m coverage report | grep TOTAL | awk '{print $NF}')
102+
echo "coverage=$TOTAL_COV" >> "$GITHUB_OUTPUT"
103+
else
104+
echo "coverage=N/A" >> "$GITHUB_OUTPUT"
105+
fi
106+
107+
# Publish general statistics for linting, test success, and code coverage as well as detailed tests results
108+
- name: Publish Consolidated Results to PR
109+
if: always() && github.event_name == 'pull_request'
110+
uses: marocchino/sticky-pull-request-comment@v2
111+
with:
112+
repo-token: ${{ secrets.GITHUB_TOKEN }}
113+
header: python-results-${{ matrix.python-version }}
114+
message: |
115+
## 🐍 Python ${{ matrix.python-version }} Results
116+
117+
| Metric | Status | Value |
118+
| :--- | :---: | :--- |
119+
| **Pylint Score** | ${{ steps.pylint.outcome == 'success' && '✅' || '⚠️' }} | `${{ steps.metrics.outputs.pylint_score }}` |
120+
| **Unit Tests** | ${{ steps.pytest.outcome == 'success' && '✅' || '❌' }} | `${{ steps.pytest.outcome }}` |
121+
| **Code Coverage** | 📊 | `${{ steps.metrics.outputs.coverage }}` |
122+
123+
[Full Workflow Logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
124+
125+
- name: Generate Job Summary
126+
if: always()
127+
run: |
128+
echo "## 🐍 Python ${{ matrix.python-version }} Execution Summary" >> $GITHUB_STEP_SUMMARY
129+
echo "" >> $GITHUB_STEP_SUMMARY
130+
echo "| Category | Status | Detail |" >> $GITHUB_STEP_SUMMARY
131+
echo "| :--- | :---: | :--- |" >> $GITHUB_STEP_SUMMARY
132+
echo "| **Pylint** | ${{ steps.pylint.outcome == 'success' && '✅' || '⚠️' }} | Score: `${{ steps.metrics.outputs.pylint_score }}` |" >> $GITHUB_STEP_SUMMARY
133+
echo "| **Pytest** | ${{ steps.pytest.outcome == 'success' && '✅' || '❌' }} | Outcome: `${{ steps.pytest.outcome }}` |" >> $GITHUB_STEP_SUMMARY
134+
echo "| **Coverage** | 📊 | Total: `${{ steps.metrics.outputs.coverage }}` |" >> $GITHUB_STEP_SUMMARY
135+
echo "" >> $GITHUB_STEP_SUMMARY
136+
echo "---" >> $GITHUB_STEP_SUMMARY
137+
138+
- name: Publish Unit Test Results
58139
if: always()
59140
uses: EnricoMi/publish-unit-test-result-action@v2
60141
with:
61142
files: tests/python/junit-${{ matrix.python-version }}.xml
62-
comment_title: Python ${{ matrix.python-version }} Test Results
143+
comment_title: Python ${{ matrix.python-version }} Detailed Test Results
63144

64145
# Explicitly fail the job if any test failed (so PRs cannot be merged with failing tests).
65146
# This runs after all reporting steps, meaning coverage and PR comments are always published.
66147
- name: Fail if tests failed
67-
if: always()
148+
if: steps.pytest.outcome == 'failure'
68149
run: |
69-
if grep -q 'failures="[1-9]' tests/python/junit-${{ matrix.python-version }}.xml; then
70-
echo "::error ::Unit tests failed. See above for details."
71-
exit 1
72-
fi
150+
echo "::error ::Unit tests failed. See above for details."
151+
exit 1
152+
153+
- name: Fail if pylint failed
154+
if: steps.pylint.outcome == 'failure'
155+
run: |
156+
echo "::error ::Pylint violations detected. See PR comment or artifacts for details."
157+
exit 1

0 commit comments

Comments
 (0)