@@ -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