feature: Add support for sysman python bindings #1
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Bindings - Sysman Python - Unit Tests & Coverage | |
| on: | |
| pull_request: | |
| branches: | |
| - '**' | |
| paths: | |
| - 'bindings/sysman/python/**.py' | |
| - 'bindings/sysman/python/test/**' | |
| - 'bindings/sysman/python/source/**' | |
| - '.github/workflows/bindings-sysman-python-unit-tests-coverage.yml' | |
| - 'bindings/sysman/python/pytest.ini' | |
| push: | |
| branches: | |
| - main | |
| - master | |
| paths: | |
| - 'bindings/sysman/python/**.py' | |
| - 'bindings/sysman/python/test/**' | |
| - 'bindings/sysman/python/source/**' | |
| - '.github/workflows/bindings-sysman-python-unit-tests-coverage.yml' | |
| - 'bindings/sysman/python/pytest.ini' | |
| workflow_dispatch: | |
| env: | |
| PYTHON_VERSION: '3.10' | |
| jobs: | |
| unit-tests-linux: | |
| name: Linux - Unit Tests & Coverage Analysis | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: bindings/sysman/python | |
| permissions: | |
| contents: read | |
| pull-requests: write # For PR comments | |
| checks: write # For check results | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # Full history for better coverage comparison | |
| - name: Set up Python ${{ env.PYTHON_VERSION }} | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| cache: 'pip' | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install pytest pytest-cov pytest-html pytest-xdist | |
| pip install coverage[toml] | |
| # Linting, formatting, and type checking tools | |
| pip install flake8 black isort mypy | |
| # Install any project-specific dependencies if requirements.txt exists | |
| if [ -f requirements.txt ]; then | |
| pip install -r requirements.txt | |
| fi | |
| - name: Code Quality Checks | |
| run: | | |
| echo "Running code quality checks..." | |
| # Check code formatting with black | |
| echo "Checking code formatting..." | |
| black --check --diff source/ test/ || { | |
| echo "❌ Code formatting issues found. Run 'black source/ test/' to fix." | |
| exit 1 | |
| } | |
| # Check import sorting | |
| echo "Checking import sorting..." | |
| isort --check-only --diff source/ test/ || { | |
| echo "❌ Import sorting issues found. Run 'isort source/ test/' to fix." | |
| exit 1 | |
| } | |
| # Run linting | |
| echo "Running flake8 linting..." | |
| flake8 source/ test/ || { | |
| echo "❌ Linting issues found. Check flake8 output above." | |
| exit 1 | |
| } | |
| # Run type checking | |
| echo "Running mypy type checking..." | |
| mypy source/ --ignore-missing-imports --no-strict-optional || { | |
| echo "❌ Type checking issues found. Check mypy output above." | |
| exit 1 | |
| } | |
| echo "✅ All code quality checks passed!" | |
| - name: Run Unit Tests with Coverage | |
| run: | | |
| # Run tests with coverage and generate multiple report formats | |
| python -m pytest test/unit_tests/ \ | |
| --cov=source \ | |
| --cov-report=term-missing \ | |
| --cov-report=html:htmlcov \ | |
| --cov-report=xml:coverage.xml \ | |
| --cov-report=json:coverage.json \ | |
| --junit-xml=test-results.xml \ | |
| --html=test-report.html \ | |
| --self-contained-html \ | |
| -v \ | |
| --tb=short \ | |
| --durations=10 | |
| - name: Extract Coverage Percentage | |
| id: coverage | |
| run: | | |
| # Extract coverage percentage from JSON report | |
| COVERAGE_PCT=$(python -c 'import json; data = json.load(open("coverage.json")); print("{:.1f}".format(data["totals"]["percent_covered"]))') | |
| echo "coverage_pct=$COVERAGE_PCT" >> $GITHUB_OUTPUT | |
| echo "Current Coverage: $COVERAGE_PCT%" | |
| - name: Get Baseline Coverage from Target Branch | |
| id: baseline | |
| run: | | |
| # Get the target branch (base of the PR or parent commit for push events) | |
| if [ "${{ github.event_name }}" == "pull_request" ]; then | |
| TARGET_BRANCH="${{ github.event.pull_request.base.ref }}" | |
| echo "Pull request detected - comparing against base branch: $TARGET_BRANCH" | |
| else | |
| # For push events, compare against the parent commit | |
| TARGET_BRANCH="${{ github.ref_name }}" | |
| echo "Push event detected - comparing against parent commit on branch: $TARGET_BRANCH" | |
| # Use HEAD~1 to get the previous commit | |
| git checkout HEAD~1 2>/dev/null || { | |
| echo "⚠️ No parent commit found (possibly first commit). Using current commit as baseline." | |
| echo "baseline_coverage=${{ steps.coverage.outputs.coverage_pct }}" >> $GITHUB_OUTPUT | |
| exit 0 | |
| } | |
| fi | |
| echo "Target branch/commit: $TARGET_BRANCH" | |
| # Checkout target branch/commit to get baseline coverage | |
| if [ "${{ github.event_name }}" == "pull_request" ]; then | |
| git fetch origin $TARGET_BRANCH | |
| git checkout origin/$TARGET_BRANCH | |
| fi | |
| # Install dependencies and run tests to get baseline coverage | |
| python -m pip install --upgrade pip | |
| pip install pytest pytest-cov coverage[toml] | |
| # Run tests with coverage for baseline | |
| python -m pytest test/unit_tests/ \ | |
| --cov=source \ | |
| --cov-report=json:baseline-coverage.json \ | |
| -q || { | |
| if [ "${{ github.event_name }}" == "pull_request" ]; then | |
| echo "❌ CRITICAL: Baseline tests failed on target branch" | |
| echo "" | |
| echo "This indicates the target branch has broken tests." | |
| echo "The PR cannot be properly validated against a broken baseline." | |
| echo "" | |
| echo "Action required:" | |
| echo " 1. Check if there's an ongoing incident with the target branch" | |
| echo " 2. Fix the target branch tests first" | |
| echo " 3. Then rebase and re-run this PR" | |
| else | |
| echo "❌ CRITICAL: Baseline tests failed on parent commit" | |
| echo "" | |
| echo "This indicates the previous commit had broken tests." | |
| echo "The current commit cannot be properly validated." | |
| fi | |
| echo "" | |
| exit 1 | |
| } | |
| # Extract baseline coverage | |
| BASELINE_COVERAGE=$(python -c 'import json; data = json.load(open("baseline-coverage.json")); print("{:.1f}".format(data["totals"]["percent_covered"]))') | |
| echo "baseline_coverage=$BASELINE_COVERAGE" >> $GITHUB_OUTPUT | |
| echo "Baseline Coverage: $BASELINE_COVERAGE%" | |
| # Switch back to PR branch | |
| git checkout ${{ github.sha }} | |
| - name: Check Coverage Threshold | |
| run: | | |
| CURRENT_COVERAGE="${{ steps.coverage.outputs.coverage_pct }}" | |
| BASELINE_COVERAGE="${{ steps.baseline.outputs.baseline_coverage }}" | |
| echo "Current Coverage: $CURRENT_COVERAGE%" | |
| echo "Target Branch Coverage: $BASELINE_COVERAGE%" | |
| # Use awk for floating point comparison (more portable than bc) | |
| if [ $(echo "$CURRENT_COVERAGE >= $BASELINE_COVERAGE" | awk '{print ($1 >= $3)}') -eq 1 ]; then | |
| DELTA=$(echo "$CURRENT_COVERAGE - $BASELINE_COVERAGE" | awk '{printf "%.1f", $1 - $3}') | |
| echo "✅ Coverage check passed: $CURRENT_COVERAGE% >= $BASELINE_COVERAGE% (Δ ${DELTA}%)" | |
| else | |
| REGRESSION=$(echo "$BASELINE_COVERAGE - $CURRENT_COVERAGE" | awk '{printf "%.1f", $1 - $3}') | |
| echo "❌ Coverage regression detected!" | |
| echo "Current coverage ($CURRENT_COVERAGE%) is below target branch coverage ($BASELINE_COVERAGE%)" | |
| echo "Regression: -${REGRESSION}%" | |
| echo "This PR would cause coverage to regress from the target branch." | |
| echo "Please add tests to maintain or improve coverage." | |
| exit 1 | |
| fi | |
| - name: Coverage Summary | |
| if: always() | |
| run: | | |
| echo "## 📊 Test Coverage Report" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY | |
| echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| Current Coverage | ${{ steps.coverage.outputs.coverage_pct }}% |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Target Branch Coverage | ${{ steps.baseline.outputs.baseline_coverage }}% |" >> $GITHUB_STEP_SUMMARY | |
| THRESHOLD="${{ steps.baseline.outputs.baseline_coverage }}" | |
| if [ $(echo "${{ steps.coverage.outputs.coverage_pct }} >= $THRESHOLD" | awk '{print ($1 >= $3)}') -eq 1 ]; then | |
| echo "| Status | ✅ PASSED |" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "| Status | ❌ FAILED |" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| # Add detailed coverage report | |
| echo "### Detailed Coverage Report" >> $GITHUB_STEP_SUMMARY | |
| echo '```' >> $GITHUB_STEP_SUMMARY | |
| python -m coverage report >> $GITHUB_STEP_SUMMARY | |
| echo '```' >> $GITHUB_STEP_SUMMARY | |
| - name: Comment PR with Coverage | |
| if: github.event_name == 'pull_request' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const coverage = '${{ steps.coverage.outputs.coverage_pct }}'; | |
| const baselineCoverage = '${{ steps.baseline.outputs.baseline_coverage }}'; | |
| const passed = parseFloat(coverage) >= parseFloat(baselineCoverage); | |
| const improvement = parseFloat(coverage) - parseFloat(baselineCoverage); | |
| const body = `## 📊 Unit Tests & Coverage Report | |
| ${passed ? '✅' : '❌'} **Coverage:** ${coverage}% vs ${baselineCoverage}% (target branch) | |
| ### Test Results | |
| - **Status:** ${passed ? 'PASSED' : 'FAILED'} | |
| - **Current Coverage:** ${coverage}% | |
| - **Target Branch Coverage:** ${baselineCoverage}% | |
| - **Change:** ${improvement > 0 ? '+' : ''}${improvement.toFixed(1)}% | |
| ${passed ? | |
| (improvement > 0 ? | |
| `🎉 Coverage improved by ${improvement.toFixed(1)}%! Great work on adding tests.` : | |
| '✅ Coverage maintained. No regression detected.') : | |
| '⚠️ Coverage regression detected. This PR would reduce test coverage. Please add more tests.' | |
| } | |
| 📁 Detailed reports are available in the workflow artifacts. | |
| `; | |
| github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: body | |
| }); | |
| - name: Upload Test Results | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: bindings-sysman-python-test-results-${{ github.run_number }} | |
| path: | | |
| test-results.xml | |
| test-report.html | |
| htmlcov/ | |
| coverage.xml | |
| coverage.json | |
| retention-days: 30 | |
| - name: Upload Coverage Reports | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: bindings-sysman-python-coverage-report-${{ github.run_number }} | |
| path: | | |
| htmlcov/ | |
| coverage.xml | |
| coverage.json | |
| retention-days: 30 | |
| # Optional: Run tests on multiple Python versions | |
| compatibility-tests: | |
| name: Python ${{ matrix.python-version }} Compatibility | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: bindings/sysman/python | |
| if: github.event_name == 'pull_request' # Only on PRs to save resources | |
| strategy: | |
| matrix: | |
| python-version: ['3.10'] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Python ${{ matrix.python-version }} | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| cache: 'pip' | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install pytest pytest-cov | |
| - name: Run Unit Tests | |
| run: | | |
| python -m pytest test/unit_tests/ \ | |
| --cov=source \ | |
| --cov-report=term-missing \ | |
| -v \ | |
| --tb=short |