| # Copyright (C) 2025 The Android Open Source Project | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| # This workflow is triggered by analyze.yml | |
| name: Perfetto CI [ui] | |
| on: | |
| workflow_call: | |
| permissions: | |
| pull-requests: write | |
| env: | |
| # /tmp/cache contains {ccache, bazelcache} and generally any other cache | |
| # that should be persisted across jobs, but only updated from the main | |
| # branch. This is populated by the "Restore cache" step below. | |
| PERFETTO_CACHE_DIR: /tmp/cache | |
| PERFETTO_ARTIFACTS_ROOT: /tmp/artifacts | |
| PYTHONUNBUFFERED: 1 | |
| jobs: | |
| ui: | |
| runs-on: self-hosted | |
| timeout-minutes: 45 | |
| env: | |
| PERFETTO_CI_BUILD_CACHE_KEY: build-cache-ui | |
| PERFETTO_CI_JOB_ID: gh-${{ github.run_id }}-${{ github.run_attempt }}-ui | |
| PERFETTO_GCS_DST: perfetto-ci-artifacts/gh-${{ github.run_id }}-${{ github.run_attempt }}-ui | |
| CI: 1 | |
| steps: | |
| - uses: actions/checkout@v5 | |
| # TODO(stevegolton): Debug - remove. | |
| - name: 'Debug GL info' | |
| shell: bash | |
| run: xvfb-run glxinfo -B | |
| # Fetch the upstream branch as well, so we can diff and see the list of | |
| # changed files (unless this is a post-submit test). | |
| - name: Fetch upstream branch | |
| if: ${{ github.base_ref != '' }} | |
| shell: bash | |
| run: git fetch origin ${{ github.base_ref }} --depth=1 | |
| - uses: ./.github/actions/install-build-deps | |
| with: | |
| install-flags: --ui | |
| - name: Restore cache | |
| uses: ./.github/actions/cache-on-google-cloud-storage/restore | |
| with: | |
| directory: ${{ env.PERFETTO_CACHE_DIR }} | |
| cache_key: ${{ env.PERFETTO_CI_BUILD_CACHE_KEY }} | |
| - name: Setup ccache | |
| uses: ./.github/actions/setup-ccache | |
| - name: Build Perfetto UI | |
| shell: bash | |
| run: | | |
| ui/build --out out/ui | |
| - name: Upload UI build to GCS | |
| shell: bash | |
| run: | | |
| gcloud storage cp --no-user-output-enabled --recursive "out/ui/ui/dist" "gs://$PERFETTO_GCS_DST/ui" | |
| echo "UI_BUILD_URL=https://storage.googleapis.com/$PERFETTO_GCS_DST/ui/index.html" >> $GITHUB_ENV | |
| - name: Comment PR with UI build link | |
| if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository }} | |
| uses: actions/github-script@v6 | |
| with: | |
| script: | | |
| const uiBuildUrl = process.env.UI_BUILD_URL; | |
| if (uiBuildUrl && context.issue.number) { | |
| // Unique marker to identify our comment | |
| const marker = "<!-- perfetto-ui-build-comment -->"; | |
| const header = `${marker}\n### 🎨 Perfetto UI Builds & Tests\n`; | |
| // Get commit info for the new entry | |
| const commitSha = context.sha.substring(0, 7); | |
| const d = new Date(); | |
| const commitDate = d.toISOString().replace('T', ' ').substring(0, 16) + ' UTC'; | |
| const newEntry = `- **${commitSha}** (${commitDate}): ${uiBuildUrl}`; | |
| // Get existing comments | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| }); | |
| // Find the bot's previous comment | |
| const botComment = comments.find(c => c.body.includes(marker)); | |
| let body; | |
| if (botComment) { | |
| // Append new entry to existing comment | |
| body = botComment.body + '\n' + newEntry; | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: botComment.id, | |
| body: body | |
| }); | |
| } else { | |
| // Create new comment with header and first entry | |
| body = header + '\n' + newEntry; | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: body | |
| }); | |
| } | |
| } | |
| - name: Print ccache stats | |
| shell: bash | |
| run: ccache --show-stats | |
| - name: UI unittests | |
| shell: bash | |
| run: ui/run-unittests --out out/ui --no-build | |
| - name: UI integrationtests | |
| id: integrationtests | |
| shell: bash | |
| run: xvfb-run ui/run-integrationtests --out out/ui --no-docker --no-build | |
| # UI code formatting checks must be done here, rather than repo-checks, | |
| # because they need a out/ui build. | |
| - name: Check UI code is formatted | |
| # base_ref is "" in post-submit, skip the presubmit check in postsubmit | |
| # as there is nothing do diff against. | |
| if: ${{ github.base_ref != '' }} | |
| shell: bash | |
| run: ui/format-sources --check-only --upstream origin/${{ github.base_ref }} | |
| - name: Upload artifacts to GCS | |
| if: ${{ always() }} | |
| env: | |
| UI_TEST_ARTIFACTS: out/ui/ui-test-artifacts | |
| shell: bash | |
| run: | | |
| if [[ ! -d "$UI_TEST_ARTIFACTS" || -n "$(find $UI_TEST_ARTIFACTS -maxdepth 0 -empty)" ]]; then | |
| echo "Directory '$UI_TEST_ARTIFACTS' is empty. Did one of the previous steps fail?" | |
| # Don't fail the step if there is nothing to upload. | |
| exit 0 | |
| fi | |
| # '--no-user-output-enabled' prevents the `cp` from printing the names | |
| # of all copied files to stdout. | |
| gcloud storage cp --no-user-output-enabled --recursive "$UI_TEST_ARTIFACTS" "gs://$PERFETTO_GCS_DST/ui-test-artifacts" | |
| # Exported only once the report has actually been uploaded, so the | |
| # PR comment step below can't link to something that doesn't exist. | |
| UI_TEST_REPORT_URL="https://storage.googleapis.com/$PERFETTO_GCS_DST/ui-test-artifacts/index.html" | |
| echo "UI_TEST_REPORT_URL=$UI_TEST_REPORT_URL" >> $GITHUB_ENV | |
| echo "UI integration test report with screnshots:" | |
| echo "$UI_TEST_REPORT_URL" | |
| echo "" | |
| echo "To download locally the changed screenshots run:" | |
| echo "tools/download_changed_screenshots.py $PERFETTO_CI_JOB_ID" | |
| echo "" | |
| echo "Perfetto UI build for this CL" | |
| echo "https://storage.googleapis.com/$PERFETTO_GCS_DST/ui/index.html" | |
| { | |
| echo "### UI test artifacts" | |
| echo "" | |
| echo "- [UI integration test report with screenshots](https://storage.googleapis.com/$PERFETTO_GCS_DST/ui-test-artifacts/index.html)" | |
| echo "- [Perfetto UI build for this CL](https://storage.googleapis.com/$PERFETTO_GCS_DST/ui/index.html)" | |
| echo "" | |
| echo "To download locally the changed screenshots run:" | |
| echo '```' | |
| echo "tools/download_changed_screenshots.py $PERFETTO_CI_JOB_ID" | |
| echo '```' | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| - name: Comment PR with UI test report link | |
| # Only when the Playwright tests themselves failed. Using | |
| # steps.integrationtests.outcome (rather than failure()) avoids posting | |
| # a "tests failed" link when an unrelated later step, e.g. the format | |
| # check, is the thing that failed. | |
| if: ${{ always() && | |
| steps.integrationtests.outcome == 'failure' && | |
| env.UI_TEST_REPORT_URL != '' && | |
| github.event_name == 'pull_request' && | |
| github.event.pull_request.head.repo.full_name == github.repository }} | |
| uses: actions/github-script@v6 | |
| with: | |
| script: | | |
| const reportUrl = process.env.UI_TEST_REPORT_URL; | |
| if (reportUrl && context.issue.number) { | |
| // Same marker as the UI build comment above: we append to that | |
| // comment rather than creating a second one. | |
| const marker = "<!-- perfetto-ui-build-comment -->"; | |
| const header = `${marker}\n### 🎨 Perfetto UI Builds & Tests\n`; | |
| // Same bullet level as the build entry it follows. | |
| const newEntry = | |
| `- ❌ **Playwright tests failed**: [test report](${reportUrl})`; | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| }); | |
| const botComment = comments.find(c => c.body.includes(marker)); | |
| let body; | |
| if (botComment) { | |
| body = botComment.body + '\n' + newEntry; | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: botComment.id, | |
| body: body | |
| }); | |
| } else { | |
| body = header + '\n' + newEntry; | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: body | |
| }); | |
| } | |
| } | |
| - name: Update cache (if on main) | |
| if: github.ref == 'refs/heads/main' | |
| uses: ./.github/actions/cache-on-google-cloud-storage/save | |
| with: | |
| directory: ${{ env.PERFETTO_CACHE_DIR }} | |
| cache_key: ${{ env.PERFETTO_CI_BUILD_CACHE_KEY }} |