backend - with test coverage #439
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: backend - with test coverage | |
| on: | |
| workflow_dispatch: | |
| workflow_call: | |
| # Every 30 minutes | |
| schedule: | |
| - cron: '0,30 * * * *' | |
| jobs: | |
| # Skip generating coverage if already exists in GCS | |
| check-existing-coverage: | |
| name: check for existing coverage | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: read | |
| id-token: write | |
| outputs: | |
| has-coverage: ${{ steps.check-coverage.outputs.exists }} | |
| commit-sha: ${{ steps.get-sha.outputs.sha }} | |
| steps: | |
| - name: Determine commit SHA | |
| id: get-sha | |
| run: | | |
| if [[ "${{ github.event_name }}" == "pull_request" ]]; then | |
| COMMIT_SHA="${{ github.event.pull_request.head.sha }}" | |
| else | |
| COMMIT_SHA="${{ github.sha }}" | |
| fi | |
| echo "sha=${COMMIT_SHA}" >> "$GITHUB_OUTPUT" | |
| - name: Authenticate to Google Cloud | |
| uses: google-github-actions/auth@c200f3691d83b41bf9bbd8638997a462592937ed # v2.1.3 | |
| with: | |
| project_id: sentry-dev-tooling | |
| workload_identity_provider: ${{ secrets.SENTRY_GCP_DEV_WORKLOAD_IDENTITY_POOL }} | |
| service_account: ${{ secrets.COLLECT_TEST_DATA_SERVICE_ACCOUNT_EMAIL }} | |
| - name: Check if coverage exists | |
| id: check-coverage | |
| env: | |
| COMMIT_SHA: ${{ steps.get-sha.outputs.sha }} | |
| run: | | |
| if gcloud storage ls "gs://sentry-coverage-data/${COMMIT_SHA}/.coverage.combined" &>/dev/null; then | |
| echo "Coverage already exists for commit ${COMMIT_SHA}, skipping test run" | |
| echo "exists=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "No coverage found for commit ${COMMIT_SHA}, will run tests" | |
| echo "exists=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| calculate-shards: | |
| name: calculate test shards | |
| if: needs.check-existing-coverage.outputs.has-coverage != 'true' | |
| needs: [check-existing-coverage] | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| outputs: | |
| shard-count: ${{ steps.calculate-shards.outputs.shard-count }} | |
| shard-indices: ${{ steps.calculate-shards.outputs.shard-indices }} | |
| steps: | |
| - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 | |
| - name: Setup sentry env | |
| uses: ./.github/actions/setup-sentry | |
| id: setup | |
| with: | |
| mode: backend-ci | |
| skip-devservices: true | |
| - name: Calculate test shards | |
| id: calculate-shards | |
| run: python3 .github/workflows/scripts/calculate-backend-test-shards.py | |
| backend-test-with-cov-context: | |
| name: backend test | |
| if: needs.check-existing-coverage.outputs.has-coverage != 'true' | |
| runs-on: ubuntu-24.04 | |
| needs: [check-existing-coverage, calculate-shards] | |
| timeout-minutes: 60 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| instance: ${{ fromJSON(needs.calculate-shards.outputs.shard-indices) }} | |
| env: | |
| MATRIX_INSTANCE_TOTAL: ${{ needs.calculate-shards.outputs.shard-count }} | |
| TEST_GROUP_STRATEGY: roundrobin | |
| steps: | |
| - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 | |
| - name: Setup sentry env | |
| uses: ./.github/actions/setup-sentry | |
| id: setup | |
| with: | |
| mode: backend-ci | |
| - name: Run backend test with coverage (${{ steps.setup.outputs.matrix-instance-number }} of ${{ steps.setup.outputs.matrix-instance-total }}) | |
| run: make test-backend-ci-with-coverage | |
| - name: Validate coverage database | |
| if: always() | |
| run: | | |
| set -euxo pipefail | |
| if [[ ! -f .coverage ]]; then | |
| echo "Error: No .coverage file found after tests" | |
| exit 1 | |
| fi | |
| python -c "import sqlite3; sqlite3.connect('.coverage').execute('SELECT COUNT(*) FROM file')" | |
| - name: Upload raw coverage sqlite as artifact | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| with: | |
| name: pycoverage-sqlite-${{ github.run_id }}-${{ steps.setup.outputs.matrix-instance-number }} | |
| path: .coverage | |
| if-no-files-found: error | |
| retention-days: 7 | |
| include-hidden-files: true | |
| combine-coverage: | |
| name: combine coverage | |
| # Only upload coverage if all test shards pass - incomplete coverage could cause selective testing to skip tests incorrectly | |
| if: needs.check-existing-coverage.outputs.has-coverage != 'true' | |
| runs-on: ubuntu-24.04 | |
| permissions: | |
| contents: read | |
| id-token: write | |
| actions: read # used for DIM metadata | |
| needs: [check-existing-coverage, backend-test-with-cov-context, calculate-shards] | |
| steps: | |
| - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 | |
| - uses: astral-sh/setup-uv@884ad927a57e558e7a70b92f2bccf9198a4be546 # v6 | |
| with: | |
| version: '0.8.2' | |
| enable-cache: false | |
| - name: Download all coverage artifacts | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 | |
| with: | |
| pattern: pycoverage-sqlite-${{ github.run_id }}-* | |
| path: .artifacts/all-coverage | |
| - name: Verify all shards produced coverage | |
| env: | |
| EXPECTED_SHARD_COUNT: ${{ needs.calculate-shards.outputs.shard-count }} | |
| run: | | |
| set -euxo pipefail | |
| echo "Downloaded coverage artifacts:" | |
| ls -la .artifacts/all-coverage || true | |
| COVERAGE_FILE_COUNT=$(find .artifacts/all-coverage -name ".coverage" -type f | wc -l) | |
| echo "Found ${COVERAGE_FILE_COUNT} coverage files, expected ${EXPECTED_SHARD_COUNT}" | |
| if [[ "$COVERAGE_FILE_COUNT" -ne "$EXPECTED_SHARD_COUNT" ]]; then | |
| echo "Error: Missing coverage files. Expected ${EXPECTED_SHARD_COUNT}, found ${COVERAGE_FILE_COUNT}" | |
| echo "This indicates some test shards failed to produce coverage." | |
| find .artifacts/all-coverage -name ".coverage" -type f | |
| exit 1 | |
| fi | |
| - name: Combine all coverage databases | |
| run: | | |
| set -euxo pipefail | |
| uvx --with covdefaults --with sentry-covdefaults-disable-branch-coverage \ | |
| coverage combine $(find .artifacts/all-coverage -name ".coverage" -type f) | |
| if [[ ! -f .coverage ]]; then | |
| echo "Error: Combined coverage file was not created" | |
| exit 1 | |
| fi | |
| mv .coverage ".coverage.combined" | |
| - name: Authenticate to Google Cloud | |
| id: gcloud-auth | |
| uses: google-github-actions/auth@c200f3691d83b41bf9bbd8638997a462592937ed # v2.1.3 | |
| with: | |
| project_id: sentry-dev-tooling | |
| workload_identity_provider: ${{ secrets.SENTRY_GCP_DEV_WORKLOAD_IDENTITY_POOL }} | |
| service_account: ${{ secrets.COLLECT_TEST_DATA_SERVICE_ACCOUNT_EMAIL }} | |
| - name: Upload coverage to GCS | |
| uses: google-github-actions/upload-cloud-storage@c0f6160ff80057923ff50e5e567695cea181ec23 # v2.2.4 | |
| with: | |
| path: .coverage.combined | |
| destination: sentry-coverage-data/${{ needs.check-existing-coverage.outputs.commit-sha }} |