Combined Coverage Artifact #1756
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: Combined Coverage Artifact | |
| on: | |
| workflow_run: | |
| workflows: ["Integration Tests"] | |
| types: | |
| - completed | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| actions: read | |
| concurrency: | |
| group: "coverage-report" | |
| cancel-in-progress: true | |
| jobs: | |
| combine: | |
| runs-on: ubuntu-latest | |
| # Only run if integration tests completed (even if some failed) | |
| if: ${{ github.event.workflow_run.conclusion == 'success' || github.event.workflow_run.conclusion == 'failure' || github.event_name == 'workflow_dispatch' }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Install coverage tools | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install coverage[toml] genbadge[coverage] | |
| - name: Download all coverage artifacts from triggering workflow | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| // Debug: Log the workflow_run context | |
| console.log('Event name:', context.eventName); | |
| console.log('Workflow run ID:', context.payload.workflow_run?.id || 'N/A'); | |
| console.log('Workflow run conclusion:', context.payload.workflow_run?.conclusion || 'N/A'); | |
| // Determine the run ID to use | |
| let runId; | |
| if (context.eventName === 'workflow_run') { | |
| runId = context.payload.workflow_run.id; | |
| console.log('Using workflow_run trigger, run ID:', runId); | |
| } else if (context.eventName === 'workflow_dispatch') { | |
| // For manual dispatch, we need to find the most recent integration test run | |
| const runs = await github.rest.actions.listWorkflowRuns({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| workflow_id: 'integration-tests.yml', | |
| per_page: 1, | |
| status: 'completed' | |
| }); | |
| if (runs.data.workflow_runs.length === 0) { | |
| throw new Error('No completed integration test runs found'); | |
| } | |
| runId = runs.data.workflow_runs[0].id; | |
| console.log('Using workflow_dispatch trigger, found most recent run ID:', runId); | |
| } else { | |
| throw new Error(`Unsupported event type: ${context.eventName}`); | |
| } | |
| // Get all artifacts from the workflow run | |
| const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| run_id: runId, | |
| }); | |
| console.log(`Found ${artifacts.data.artifacts.length} total artifacts`); | |
| // Filter for coverage artifacts | |
| const coverageArtifacts = artifacts.data.artifacts.filter(a => a.name.startsWith('coverage-')); | |
| console.log(`Found ${coverageArtifacts.length} coverage artifacts:`, coverageArtifacts.map(a => a.name)); | |
| if (coverageArtifacts.length === 0) { | |
| console.log('No coverage artifacts found. This may be expected if tests were skipped or failed early.'); | |
| // Create empty directory so subsequent steps don't fail | |
| const coverageDir = 'coverage-reports'; | |
| if (!fs.existsSync(coverageDir)) { | |
| fs.mkdirSync(coverageDir, { recursive: true }); | |
| } | |
| return; | |
| } | |
| // Create coverage-reports directory | |
| const coverageDir = 'coverage-reports'; | |
| if (!fs.existsSync(coverageDir)) { | |
| fs.mkdirSync(coverageDir, { recursive: true }); | |
| } | |
| // Download each coverage artifact | |
| for (const artifact of coverageArtifacts) { | |
| console.log(`Downloading artifact: ${artifact.name}`); | |
| const download = await github.rest.actions.downloadArtifact({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| artifact_id: artifact.id, | |
| archive_format: 'zip', | |
| }); | |
| // Save the zip file | |
| const zipPath = path.join(coverageDir, `${artifact.name}.zip`); | |
| fs.writeFileSync(zipPath, Buffer.from(download.data)); | |
| // Unzip the artifact | |
| const artifactDir = path.join(coverageDir, artifact.name); | |
| if (!fs.existsSync(artifactDir)) { | |
| fs.mkdirSync(artifactDir, { recursive: true }); | |
| } | |
| // Use unzip command to extract | |
| const { execSync } = require('child_process'); | |
| execSync(`unzip -q "${zipPath}" -d "${artifactDir}"`); | |
| // Remove the zip file | |
| fs.unlinkSync(zipPath); | |
| console.log(`Extracted ${artifact.name} to ${artifactDir}`); | |
| } | |
| - name: Combine coverage reports | |
| run: | | |
| mkdir -p site | |
| # Check if coverage-reports directory exists and has content | |
| if [ ! -d "coverage-reports" ] || [ -z "$(ls -A coverage-reports 2>/dev/null)" ]; then | |
| echo "No coverage reports found. Creating empty report." | |
| echo "<html><body><h1>No Coverage Data</h1><p>No coverage artifacts were found from the integration tests.</p></body></html>" > site/index.html | |
| exit 0 | |
| fi | |
| # Find all .coverage files | |
| echo "=== Coverage files found ===" | |
| find coverage-reports -name ".coverage*" -type f || echo "No .coverage files found" | |
| # Rename all .coverage files to have unique names for combining | |
| # coverage combine expects files named .coverage.* | |
| cd coverage-reports | |
| coverage_count=0 | |
| for dir in */; do | |
| if [ -f "$dir/.coverage" ]; then | |
| # Extract job name from directory | |
| job_name=$(basename "$dir") | |
| mv "$dir/.coverage" ".coverage.$job_name" | |
| echo "Renamed $dir/.coverage to .coverage.$job_name" | |
| coverage_count=$((coverage_count + 1)) | |
| fi | |
| if [ -f "$dir/.coverage.wheel" ]; then | |
| mv "$dir/.coverage.wheel" ".coverage.wheel" | |
| echo "Found wheel coverage file" | |
| coverage_count=$((coverage_count + 1)) | |
| fi | |
| done | |
| cd .. | |
| if [ $coverage_count -eq 0 ]; then | |
| echo "No .coverage files found in artifacts. Creating empty report." | |
| echo "<html><body><h1>No Coverage Data</h1><p>Coverage artifacts were downloaded but contained no .coverage files.</p></body></html>" > site/index.html | |
| exit 0 | |
| fi | |
| # Combine all .coverage files | |
| coverage combine coverage-reports/.coverage.* || echo "Coverage combine completed with warnings" | |
| # Generate combined reports | |
| coverage xml -o coverage.xml | |
| coverage html -d site/coverage | |
| echo "=== Combined coverage report generated ===" | |
| ls -la coverage.xml site/coverage/ | |
| - name: Generate coverage badge | |
| run: | | |
| if [ ! -f coverage.xml ]; then | |
| echo "coverage.xml not found; skipping coverage badge." | |
| exit 0 | |
| fi | |
| mkdir -p .github/badges | |
| genbadge coverage -i coverage.xml -o .github/badges/coverage.svg -n "coverage report" | |
| - name: Create index.html and README | |
| run: | | |
| # Create index.html for redirection | |
| if [ -d site/coverage ]; then | |
| cat > site/index.html << 'EOF' | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta http-equiv="refresh" content="0; url=./coverage/"> | |
| <title>Redirecting to coverage report...</title> | |
| </head> | |
| <body> | |
| <p>Redirecting to coverage report... <a href="./coverage/">Click here if not redirected</a></p> | |
| </body> | |
| </html> | |
| EOF | |
| else | |
| echo "No HTML coverage directory found; preserving no-data index." | |
| fi | |
| # Create README.md in the site directory | |
| cat > site/README.md << 'EOF' | |
| # OpenHCS Code Coverage Reports | |
| This site contains the combined code coverage reports for the [OpenHCS](https://github.com/OpenHCSDev/OpenHCS) project. | |
| ## Navigation | |
| - [Coverage Report](./coverage/): View the HTML coverage report | |
| ## About | |
| These reports are automatically generated by GitHub Actions and combine coverage from all integration test jobs: | |
| - Python boundary tests (3.11, 3.13) across Linux, Windows, macOS | |
| - Backend/microscope combinations (disk, zarr × ImageXpress, OperaPhenix) across all OSes | |
| - OMERO tests (Linux only, Python 3.11-3.12) | |
| - Wheel installation tests | |
| The combined coverage shows the total percentage of code covered by all test suites together. | |
| Last updated: $(date) | |
| EOF | |
| # Disabled: Auto-committing badges causes push conflicts | |
| # - name: Commit and push if coverage badge changed | |
| # if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| # run: | | |
| # git config --local user.email "github-actions[bot]@users.noreply.github.com" | |
| # git config --local user.name "github-actions[bot]" | |
| # git add .github/badges/coverage.svg -f | |
| # git commit -m "chore: update coverage badge" || exit 0 | |
| # git push | |
| - name: Upload combined coverage report | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: combined-coverage-report | |
| path: | | |
| site | |
| coverage.xml | |
| .github/badges/coverage.svg | |
| if-no-files-found: warn | |
| retention-days: 14 |