Skip to content

feature: Add support for sysman python bindings #1

feature: Add support for sysman python bindings

feature: Add support for sysman python bindings #1

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