PR Emulator.wtf #893
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: PR Emulator.wtf | |
| on: # yamllint disable-line rule:truthy | |
| workflow_run: | |
| workflows: ['Pull Request'] | |
| types: [completed] | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }} | |
| cancel-in-progress: true | |
| permissions: {} | |
| jobs: | |
| emulator_wtf: | |
| name: "Instrumentation Test app" | |
| runs-on: ubuntu-latest | |
| environment: ui-test | |
| if: > | |
| github.event.workflow_run.event == 'pull_request' | |
| && github.event.workflow_run.conclusion == 'success' | |
| permissions: | |
| id-token: write # Needed for OIDC authentication | |
| actions: read # Needed for retrieving artifacts | |
| checks: write # Needed to publish this job's status as a PR check | |
| steps: | |
| # Surface this job's own success/failure as a check on the PR commit. | |
| # workflow_run jobs are detached from the PR, so the status is not shown | |
| # otherwise. A check run created via the API for a PR-head SHA gets grouped | |
| # under an arbitrary existing check suite on that commit (here CodeQL's, so | |
| # it renders as "CodeQL / Instrumentation Test app (dynamic)"); the suite | |
| # cannot be chosen via the API. We accept that prefix to stay consistent | |
| # with the publish-unit-test-result-action check below, which has the same | |
| # limitation. Every input here comes from the trusted workflow_run context | |
| # (no fork-controlled data is consumed). | |
| - name: Create PR check (in progress) | |
| id: create_check | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| HEAD_SHA: ${{ github.event.workflow_run.head_sha }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| # On PR-context check_suites GitHub absorbs API-created check_runs into | |
| # the triggering "Pull Request" workflow's suite and silently rewrites | |
| # details_url to the legacy `runs/{check_run_id}` form, which lands on | |
| # an empty synthetic-job page in the parent run. We can't prevent that, | |
| # but a Markdown link in output.summary renders on whichever page the | |
| # Details button ends up showing, so the real emulator.wtf job logs | |
| # are always one click away. | |
| run: | | |
| # Identify this job by its runner, not by display name. GitHub does | |
| # not expose the current job's database id directly (no GITHUB_JOB_ID, | |
| # no /actions/jobs/current endpoint — confirmed against the REST API | |
| # docs), and $GITHUB_JOB is the YAML key, not the id we need for the | |
| # URL. The jobs API returns a `runner_name` field on every job, and | |
| # the runner exports its name as $RUNNER_NAME, so matching the two | |
| # uniquely identifies this job regardless of how many siblings exist. | |
| # `env.RUNNER_NAME` is jq's safe way to read the env var (no shell | |
| # interpolation of the value into the filter string). | |
| JOB_ID=$(gh api "repos/$REPO/actions/runs/$GITHUB_RUN_ID/jobs?per_page=100" \ | |
| --jq '[.jobs[] | select(.runner_name == env.RUNNER_NAME) | .id][0]' || true) | |
| # Defence-in-depth: only use JOB_ID in a URL if it looks like an id. | |
| # gh returns integers here, but validating keeps the URL safe even if | |
| # GitHub's response format ever changes, and guards against the | |
| # selector ever matching zero or multiple jobs. | |
| if [[ "$JOB_ID" =~ ^[0-9]+$ ]]; then | |
| JOB_URL="$RUN_URL/job/$JOB_ID" | |
| else | |
| JOB_URL="$RUN_URL" | |
| fi | |
| SUMMARY="Full logs: [open this emulator.wtf job →]($JOB_URL)" | |
| check_id=$(jq -n \ | |
| --arg sha "$HEAD_SHA" \ | |
| --arg url "$JOB_URL" \ | |
| --arg summary "$SUMMARY" \ | |
| '{ | |
| name: "Instrumentation Test app", | |
| head_sha: $sha, | |
| status: "in_progress", | |
| details_url: $url, | |
| output: {title: "Running on emulator.wtf", summary: $summary} | |
| }' \ | |
| | gh api --method POST "repos/$REPO/check-runs" --input - --jq '.id') | |
| { | |
| echo "check_id=$check_id" | |
| echo "job_url=$JOB_URL" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Download emulator.wtf inputs | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: emulator-wtf-inputs | |
| run-id: ${{ github.event.workflow_run.id }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| path: emulator-wtf-inputs | |
| - name: Load device list | |
| id: devices | |
| # The artifact comes from a PR build that may originate from a fork; validate | |
| # every line strictly before piping the contents into GITHUB_OUTPUT to prevent | |
| # heredoc-terminator injection. | |
| run: | | |
| if [ ! -s emulator-wtf-inputs/devices.txt ]; then | |
| echo "::error::devices.txt is empty or missing" | |
| exit 1 | |
| fi | |
| while IFS= read -r line; do | |
| if [[ ! "$line" =~ ^model=Pixel7,version=(2[3-9]|3[0-9]|40)$ ]]; then | |
| echo "::error::Invalid line in devices.txt" | |
| exit 1 | |
| fi | |
| done < emulator-wtf-inputs/devices.txt | |
| { | |
| echo 'list<<DEVICES_EOF' | |
| cat emulator-wtf-inputs/devices.txt | |
| echo 'DEVICES_EOF' | |
| } >> "$GITHUB_OUTPUT" | |
| - uses: emulator-wtf/actions/configure-credentials@72f86422814fad7432a286a1b29699cc9e73d561 # v1.1.0 | |
| with: | |
| oidc-configuration-id: ${{ vars.EMULATOR_WTF_OIDC_CONFIGURATION_ID }} | |
| - name: Run tests full | |
| uses: emulator-wtf/actions/run-tests@72f86422814fad7432a286a1b29699cc9e73d561 # v1.1.0 | |
| with: | |
| devices: ${{ steps.devices.outputs.list }} | |
| app: emulator-wtf-inputs/app/build/outputs/apk/full/debug/app-full-debug.apk | |
| test: emulator-wtf-inputs/app/build/outputs/apk/androidTest/full/debug/app-full-debug-androidTest.apk | |
| outputs-dir: build/test-results/full | |
| - name: Run tests minimal | |
| uses: emulator-wtf/actions/run-tests@72f86422814fad7432a286a1b29699cc9e73d561 # v1.1.0 | |
| with: | |
| devices: ${{ steps.devices.outputs.list }} | |
| app: emulator-wtf-inputs/app/build/outputs/apk/minimal/debug/app-minimal-debug.apk | |
| test: emulator-wtf-inputs/app/build/outputs/apk/androidTest/minimal/debug/app-minimal-debug-androidTest.apk | |
| outputs-dir: build/test-results/minimal | |
| - name: Upload test results | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: Instrumentation Test app results | |
| path: | | |
| **/build/test-results/**/TEST-*.xml | |
| **/build/test-results/**/results.xml | |
| - name: Upload test reports | |
| if: failure() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: Instrumentation Test app reports | |
| path: build/test-results/** | |
| # job.status reflects everything above (check creation, downloads, test runs) and is | |
| # one of success/failure/cancelled, all valid check-run conclusions. | |
| # We re-send the same output.summary link so the completed check page | |
| # keeps the deep-link to the emulator.wtf job logs (see the Create step | |
| # for why this matters on PR-context check_suites). | |
| - name: Finalize PR check | |
| if: always() && steps.create_check.outputs.check_id != '' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| CHECK_ID: ${{ steps.create_check.outputs.check_id }} | |
| CONCLUSION: ${{ job.status }} | |
| JOB_URL: ${{ steps.create_check.outputs.job_url }} | |
| run: | | |
| SUMMARY="Full logs: [open this emulator.wtf job →]($JOB_URL)" | |
| jq -n \ | |
| --arg conclusion "$CONCLUSION" \ | |
| --arg summary "$SUMMARY" \ | |
| '{ | |
| status: "completed", | |
| conclusion: $conclusion, | |
| output: {title: "Completed on emulator.wtf", summary: $summary} | |
| }' \ | |
| | gh api --method PATCH "repos/$REPO/check-runs/$CHECK_ID" --input - | |
| publish_test_results: | |
| name: "Publish Tests Results" | |
| needs: [emulator_wtf] | |
| if: always() | |
| runs-on: ubuntu-latest | |
| permissions: | |
| checks: write | |
| pull-requests: write | |
| actions: read | |
| contents: read | |
| steps: | |
| - name: Download artifacts from pr.yml run | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| run-id: ${{ github.event.workflow_run.id }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| path: pr-artifacts | |
| # If emulator_wtf was skipped (non-PR trigger or triggering workflow did not succeed) or | |
| # cancelled/failed before running tests, it uploads no results artifact, and | |
| # downloading it by name errors. Tolerate that so we still publish the | |
| # pr.yml results below. We cannot gate on needs.emulator_wtf.result because | |
| # a real test failure also fails the job yet does produce this artifact, | |
| # which we must still publish. | |
| - name: Download emulator.wtf results from this run | |
| continue-on-error: true | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: Instrumentation Test app results | |
| path: pr-artifacts/Instrumentation Test app results | |
| # Build a minimal event payload from the trusted workflow_run context. | |
| # Supplying any event_file flips the is_fork check to false in the | |
| # publish-unit-test-result-action source | |
| # (publish_test_results.py: `is_fork = event_file is None and ...`), so it | |
| # publishes the check run instead of disabling it for fork PRs. We build | |
| # the payload here from trusted context rather than forwarding the real | |
| # pull_request payload, which on a fork PR could be forged. | |
| # The PR for the failures comment is resolved from the commit SHA via the | |
| # API (the action's publisher.py get_pulls_from_commit), so no PR number | |
| # is needed here. | |
| # | |
| # This payload is intentionally minimal: it was verified sufficient against | |
| # publish-unit-test-result-action (the pinned SHA below). Every | |
| # other event field the action reads degrades gracefully when absent (it | |
| # only drops the cosmetic "vs base"/"vs earlier" deltas). If you bump the | |
| # action version, re-check which event fields it reads and update this. | |
| - name: Build minimal event payload | |
| env: | |
| HEAD_REPO: ${{ github.event.workflow_run.head_repository.full_name }} | |
| run: | | |
| jq -n --arg head "$HEAD_REPO" \ | |
| '{pull_request: {head: {repo: {full_name: $head}}}}' > event.json | |
| - name: Publish Test Results | |
| uses: EnricoMi/publish-unit-test-result-action@d0a4676d0e0b938bc201470d88276b7c74c712b3 # v2.24.0 | |
| with: | |
| commit: ${{ github.event.workflow_run.head_sha }} | |
| event_name: ${{ github.event.workflow_run.event }} | |
| event_file: "event.json" | |
| comment_mode: "failures" | |
| action_fail: true | |
| files: | | |
| pr-artifacts/**/TEST-*.xml | |
| pr-artifacts/**/results.xml |