Add PHPUnit Coverage workflow #16
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: Code Coverage | |
| on: | |
| pull_request: | |
| branches: | |
| - develop | |
| - main | |
| push: | |
| branches: | |
| - develop | |
| - main | |
| workflow_dispatch: | |
| # Cancels all previous workflow runs for the same branch that have not yet completed. | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| code-coverage: | |
| name: Code Coverage Check | |
| runs-on: ubuntu-latest | |
| services: | |
| mysql: | |
| image: mysql:8.0 | |
| env: | |
| MYSQL_ALLOW_EMPTY_PASSWORD: false | |
| MYSQL_ROOT_PASSWORD: root | |
| MYSQL_DATABASE: wordpress_tests | |
| ports: | |
| - 3306:3306 | |
| options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=10s --health-retries=10 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # Fetch all history for accurate diff | |
| - name: Install PHP | |
| uses: shivammathur/setup-php@v2 | |
| with: | |
| php-version: '8.2' | |
| ini-values: zend.assertions=1, error_reporting=-1, display_errors=On | |
| coverage: pcov # Use PCOV for faster code coverage (5x faster than Xdebug) | |
| tools: composer | |
| - name: Install SVN | |
| run: sudo apt-get install subversion | |
| - name: "Composer: remove the PHP platform requirement" | |
| run: composer config --unset platform.php | |
| - name: Install Composer dependencies | |
| uses: ramsey/composer-install@v2 | |
| with: | |
| dependency-versions: "highest" | |
| composer-options: "yoast/wp-test-utils --with-dependencies" | |
| custom-cache-suffix: $(date -u -d "-0 month -$(($(date +%d)-1)) days" "+%F") | |
| - name: Install WordPress Test Suite | |
| shell: bash | |
| run: tests/bin/install-wp-tests.sh wordpress_tests root root 127.0.0.1:3306 latest | |
| - name: Generate code coverage report for current branch | |
| run: | | |
| echo "=== Debug: Check PCOV is loaded ===" | |
| php -m | grep pcov || echo "PCOV not loaded" | |
| php -r "var_dump(extension_loaded('pcov'));" | |
| echo "=== Debug: Check PHPUnit version and php-code-coverage ===" | |
| vendor/bin/phpunit --version | |
| php -r "echo class_exists('SebastianBergmann\CodeCoverage\CodeCoverage') ? 'CodeCoverage class exists' : 'CodeCoverage class NOT found'; echo PHP_EOL;" | |
| echo "=== Debug: Before running PHPUnit ===" | |
| pwd | |
| ls -la | |
| echo "=== Running PHPUnit with coverage (without piping to tee) ===" | |
| php -d pcov.enabled=1 -d pcov.directory=. vendor/bin/phpunit --dont-report-useless-tests --coverage-clover=coverage.xml --coverage-text 2>&1 | tail -100 | |
| PHPUNIT_EXIT=$? | |
| echo "=== Debug: PHPUnit exit code: $PHPUNIT_EXIT ===" | |
| echo "=== Debug: After running PHPUnit ===" | |
| pwd | |
| ls -la coverage* 2>/dev/null || echo "No coverage files in current directory" | |
| echo "=== Searching entire workspace for coverage.xml ===" | |
| find . -name "coverage.xml" -type f 2>/dev/null || echo "No coverage.xml found anywhere" | |
| echo "=== Checking if coverage report was generated ===" | |
| if [ -f coverage.xml ]; then | |
| echo "SUCCESS: coverage.xml exists!" | |
| ls -lh coverage.xml | |
| else | |
| echo "FAIL: coverage.xml was not generated" | |
| echo "=== Checking stderr for errors ===" | |
| php -d pcov.enabled=1 -d pcov.directory=. vendor/bin/phpunit --dont-report-useless-tests --coverage-clover=coverage.xml --coverage-text 2>&1 >/dev/null | grep -i "error\|warning\|coverage" || echo "No error messages found" | |
| fi | |
| continue-on-error: false | |
| - name: Upload coverage to Codecov | |
| uses: codecov/codecov-action@v4 | |
| with: | |
| files: ./coverage.xml | |
| flags: unittests | |
| name: codecov-progress-planner | |
| fail_ci_if_error: false | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| - name: Generate coverage report summary | |
| id: coverage | |
| run: | | |
| # Debug: Check for coverage files | |
| echo "=== Debug: Looking for coverage files ===" | |
| ls -la coverage* 2>/dev/null || echo "No coverage files found in current directory" | |
| echo "=== Debug: Current directory ===" | |
| pwd | |
| echo "=== Debug: Workspace directory ===" | |
| echo "${{ github.workspace }}" | |
| # Search for coverage.xml anywhere in the workspace | |
| echo "=== Searching for coverage.xml recursively ===" | |
| FOUND_FILES=$(find ${{ github.workspace }} -name "coverage.xml" -type f 2>/dev/null) | |
| if [ -n "$FOUND_FILES" ]; then | |
| echo "Found coverage.xml files:" | |
| echo "$FOUND_FILES" | |
| else | |
| echo "No coverage.xml found anywhere in workspace" | |
| fi | |
| # Try to find coverage.xml in multiple locations | |
| COVERAGE_FILE="" | |
| if [ -f coverage.xml ]; then | |
| COVERAGE_FILE="coverage.xml" | |
| echo "Found coverage.xml in current directory" | |
| elif [ -f ${{ github.workspace }}/coverage.xml ]; then | |
| COVERAGE_FILE="${{ github.workspace }}/coverage.xml" | |
| echo "Found coverage.xml in workspace root" | |
| else | |
| # Try to find it anywhere | |
| COVERAGE_FILE=$(find ${{ github.workspace }} -name "coverage.xml" -type f 2>/dev/null | head -1) | |
| if [ -n "$COVERAGE_FILE" ]; then | |
| echo "Found coverage.xml at: $COVERAGE_FILE" | |
| fi | |
| fi | |
| if [ -n "$COVERAGE_FILE" ]; then | |
| echo "=== Debug: Found coverage file at $COVERAGE_FILE ===" | |
| echo "=== Debug: First 50 lines of coverage.xml ===" | |
| head -50 "$COVERAGE_FILE" | |
| # Extract coverage using various possible attribute names | |
| # Try 'statements' and 'coveredstatements' | |
| LINES=$(grep -o 'statements="[0-9]*"' "$COVERAGE_FILE" | head -1 | grep -o '[0-9]*') | |
| COVERED=$(grep -o 'coveredstatements="[0-9]*"' "$COVERAGE_FILE" | head -1 | grep -o '[0-9]*') | |
| # Try alternative: 'elements' and 'coveredelements' | |
| if [ -z "$LINES" ]; then | |
| LINES=$(grep -o 'elements="[0-9]*"' "$COVERAGE_FILE" | head -1 | grep -o '[0-9]*') | |
| COVERED=$(grep -o 'coveredelements="[0-9]*"' "$COVERAGE_FILE" | head -1 | grep -o '[0-9]*') | |
| fi | |
| echo "=== Debug: LINES=$LINES, COVERED=$COVERED ===" | |
| if [ -n "$LINES" ] && [ -n "$COVERED" ] && [ "$LINES" -gt 0 ]; then | |
| COVERAGE=$(awk "BEGIN {printf \"%.2f\", ($COVERED/$LINES)*100}") | |
| else | |
| COVERAGE="0" | |
| fi | |
| else | |
| echo "=== Debug: No coverage.xml file found ===" | |
| COVERAGE="0" | |
| fi | |
| echo "current_coverage=$COVERAGE" >> $GITHUB_OUTPUT | |
| echo "Current code coverage: $COVERAGE%" | |
| - name: Checkout base branch for comparison | |
| if: github.event_name == 'pull_request' | |
| run: | | |
| git fetch origin ${{ github.base_ref }} | |
| git checkout origin/${{ github.base_ref }} | |
| - name: Install dependencies on base branch | |
| if: github.event_name == 'pull_request' | |
| run: | | |
| composer install --no-interaction --prefer-dist --optimize-autoloader | |
| - name: Generate coverage report for base branch | |
| if: github.event_name == 'pull_request' | |
| id: base_coverage | |
| run: | | |
| # Generate coverage for base branch | |
| vendor/bin/phpunit --coverage-text --colors=never > base-coverage.txt 2>&1 || true | |
| BASE_COVERAGE=$(cat base-coverage.txt | grep "Lines:" | awk '{print $2}' | sed 's/%//' || echo "0") | |
| echo "base_coverage=$BASE_COVERAGE" >> $GITHUB_OUTPUT | |
| echo "Base branch code coverage: $BASE_COVERAGE%" | |
| continue-on-error: true | |
| - name: Compare coverage and enforce threshold | |
| if: github.event_name == 'pull_request' | |
| run: | | |
| CURRENT="${{ steps.coverage.outputs.current_coverage }}" | |
| BASE="${{ steps.base_coverage.outputs.base_coverage }}" | |
| # Default to 0 if base coverage couldn't be determined | |
| BASE=${BASE:-0} | |
| echo "Current Coverage: $CURRENT%" | |
| echo "Base Coverage: $BASE%" | |
| # Calculate the difference | |
| DIFF=$(echo "$CURRENT - $BASE" | bc) | |
| echo "Coverage Difference: $DIFF%" | |
| # Check if coverage dropped by more than 0.5% | |
| THRESHOLD=-0.5 | |
| if (( $(echo "$DIFF < $THRESHOLD" | bc -l) )); then | |
| echo "❌ Code coverage dropped by ${DIFF}%, which exceeds the allowed threshold of ${THRESHOLD}%" | |
| echo "Please add tests to maintain or improve code coverage." | |
| exit 1 | |
| else | |
| echo "✅ Code coverage check passed!" | |
| echo "Coverage change: ${DIFF}%" | |
| fi | |
| - name: Comment PR with coverage | |
| if: github.event_name == 'pull_request' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const current = '${{ steps.coverage.outputs.current_coverage }}'; | |
| const base = '${{ steps.base_coverage.outputs.base_coverage }}' || '0'; | |
| const diff = (parseFloat(current) - parseFloat(base)).toFixed(2); | |
| const emoji = diff >= 0 ? '📈' : '📉'; | |
| const status = diff >= -0.5 ? '✅' : '❌'; | |
| const comment = `## ${status} Code Coverage Report | |
| | Metric | Value | | |
| |--------|-------| | |
| | Current Coverage | **${current}%** | | |
| | Base Coverage | **${base}%** | | |
| | Difference | ${emoji} **${diff}%** | | |
| ${diff < -0.5 ? '⚠️ **Warning:** Coverage dropped by more than 0.5%. Please add tests.' : ''} | |
| ${diff >= 0 ? '🎉 Great job maintaining/improving code coverage!' : ''} | |
| `; | |
| // Find existing coverage report comment | |
| const {data: comments} = await github.rest.issues.listComments({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| }); | |
| const botComment = comments.find(comment => | |
| comment.user.type === 'Bot' && | |
| comment.body.includes('Code Coverage Report') | |
| ); | |
| if (botComment) { | |
| // Update existing comment | |
| await github.rest.issues.updateComment({ | |
| comment_id: botComment.id, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: comment | |
| }); | |
| } else { | |
| // Create new comment | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: comment | |
| }); | |
| } | |
| - name: Generate HTML coverage report | |
| if: always() | |
| run: | | |
| vendor/bin/phpunit --coverage-html=coverage-html | |
| continue-on-error: true | |
| - name: Upload HTML coverage report as artifact | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-report | |
| path: coverage-html/ | |
| retention-days: 30 |