Skip to content

fix(core): flank strikethrough tildes like asterisk emphasis, matching GitHub #26

fix(core): flank strikethrough tildes like asterisk emphasis, matching GitHub

fix(core): flank strikethrough tildes like asterisk emphasis, matching GitHub #26

name: Android benchmarks
# Label a pull request `benchmark: android` (later pushes re-run it while the
# label stays), comment `/benchmark android` on it, or dispatch with its number,
# to compare its head against main on one emulator and post the table.
# Forks run only at a reviewed commit: `/benchmark android <full head SHA>`.
on:
pull_request:
types: [labeled, synchronize]
issue_comment:
types: [created]
workflow_dispatch:
inputs:
pull_request:
description: Pull request number
required: true
type: number
commit:
description: Full SHA of the head commit you reviewed (required for a pull request from a fork)
required: false
type: string
jobs:
resolve:
# Runs pull request code, so it needs write access. The association is a
# cheap prefilter; the permission check below decides. The label skips
# forks, which have no reviewed commit to pin and get a read-only token.
if: >-
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && (
(github.event.action == 'labeled' && github.event.label.name == 'benchmark: android')
|| (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'benchmark: android'))))
|| (github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/benchmark android') && contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association))
|| github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
pull-requests: read
outputs:
number: ${{ steps.pr.outputs.number }}
head: ${{ steps.pr.outputs.head }}
base: ${{ steps.pr.outputs.base }}
behind: ${{ steps.pr.outputs.behind }}
steps:
# Labeling needs only triage access, so the labeler is checked too.
- name: Check the requester's permission
if: github.event_name != 'workflow_dispatch'
env:
GH_TOKEN: ${{ github.token }}
REQUESTER: ${{ github.event.sender.login }}
run: |
set -euo pipefail
PERMISSION=$(gh api "repos/$GITHUB_REPOSITORY/collaborators/$REQUESTER/permission" --jq .permission)
# maintain reads as write here, and triage as read.
if [[ "$PERMISSION" != admin && "$PERMISSION" != write ]]; then
echo "::error::@$REQUESTER has $PERMISSION access; a benchmark needs write access"
exit 1
fi
- name: Acknowledge the comment
if: github.event_name == 'issue_comment'
env:
GH_TOKEN: ${{ github.token }}
run: |
gh api --method POST "repos/$GITHUB_REPOSITORY/issues/comments/${{ github.event.comment.id }}/reactions" \
-f content=rocket --silent || true
# Pins both commits, so a push during the run changes nothing. A fork is
# measured only at the commit the requester reviewed.
- name: Resolve the commits
id: pr
env:
GH_TOKEN: ${{ github.token }}
EVENT: ${{ github.event_name }}
NUMBER: ${{ github.event.pull_request.number || github.event.issue.number || inputs.pull_request }}
BODY: ${{ github.event.comment.body }}
COMMIT: ${{ inputs.commit }}
run: |
set -euo pipefail
refuse() {
echo "::error::$1"
if [[ "$EVENT" == issue_comment ]]; then
gh api "repos/$GITHUB_REPOSITORY/issues/$NUMBER/comments" -f body="$1" --silent || true
fi
exit 1
}
# `/benchmark android [commit]`, on the first line of the comment.
REQUESTED=$COMMIT
if [[ "$EVENT" == issue_comment ]]; then
read -r _ _ REQUESTED _ <<< "$(head -n 1 <<< "$BODY" | tr -d '\r')"
fi
REQUESTED=${REQUESTED:-}
if [[ -n "$REQUESTED" && ! "$REQUESTED" =~ ^[0-9a-f]{40}$ ]]; then
refuse "Name the commit with its full 40-character SHA, as in \`/benchmark android <SHA>\`."
fi
PR=$(gh pr view "$NUMBER" --repo "$GITHUB_REPOSITORY" --json headRefOid,isCrossRepository \
--jq '"\(.headRefOid) \(.isCrossRepository)"')
HEAD=${PR% *}
FORK=${PR#* }
BASE=$(gh api "repos/$GITHUB_REPOSITORY/commits/main" --jq .sha)
if [[ -z "$HEAD" || -z "$BASE" ]]; then
echo "::error::Could not resolve the head of pull request #$NUMBER or the tip of main"
exit 1
fi
if [[ -z "$REQUESTED" && "$FORK" == true ]]; then
refuse "#$NUMBER comes from a fork, so the benchmark runs its code only at a commit you name. Review the head, then comment \`/benchmark android $HEAD\`."
fi
if [[ -n "$REQUESTED" && "$REQUESTED" != "$HEAD" ]]; then
refuse "The head of #$NUMBER is now \`$HEAD\`, not the requested \`$REQUESTED\`. Review what changed, then request the benchmark at the new head."
fi
# Informational only: a head behind main also measures what main gained since.
BEHIND=$(gh api "repos/$GITHUB_REPOSITORY/compare/$BASE...$HEAD" --jq .behind_by || echo "")
{
echo "number=$NUMBER"
echo "head=$HEAD"
echo "base=$BASE"
echo "behind=$BEHIND"
} >> "$GITHUB_OUTPUT"
benchmark:
needs: resolve
runs-on: ubuntu-latest
timeout-minutes: 90
# A newer request cancels this one. Set on the job, not the workflow, so
# only a request that passed resolve can cancel.
concurrency:
group: android-benchmarks-${{ needs.resolve.outputs.number }}
cancel-in-progress: true
# Runs the pull request's code, so the token can only read.
permissions:
contents: read
steps:
# Tooling comes from main; the pull request is built in a worktree, which
# shares .git/config, so no credentials are persisted.
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
ref: ${{ needs.resolve.outputs.base }}
fetch-depth: 0
persist-credentials: false
# Pull requests from forks are only reachable through their pull ref.
- name: Fetch the pull request
env:
NUMBER: ${{ needs.resolve.outputs.number }}
HEAD: ${{ needs.resolve.outputs.head }}
run: |
git fetch --no-tags origin "+refs/pull/$NUMBER/head:refs/remotes/pull/$NUMBER"
git cat-file -e "$HEAD^{commit}"
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: .nvmrc
# Restore only: a cache saved from this run would reach later runs on main.
- name: Setup Android
uses: ./.github/actions/setup-android
with:
gradle-wrapper-properties: packages/enriched-markdown-android/gradle/wrapper/gradle-wrapper.properties
cache: restore
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
# ATD images run fewer background services, so there is less noise. The
# screen stays unlocked; the benchmark library refuses a locked one. The
# build runs without the Actions runtime token, so its tooling cannot
# save caches to main by accident. Code that wants the token can still
# read it from the runner, so reviewing what runs here is the safeguard.
# A step that runs out of time fails, while a job that does is cancelled
# and would read as superseded, so the limit is set here, under the job's.
- name: Compare the pull request against main
timeout-minutes: 75
uses: ReactiveCircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # v2.38.0
with:
api-level: 35
target: aosp_atd
arch: x86_64
profile: pixel_6
cores: 2
ram-size: 4096M
script: |
adb shell svc power stayon true
adb shell input keyevent KEYCODE_WAKEUP
adb shell wm dismiss-keyguard
env -u ACTIONS_RUNTIME_TOKEN -u ACTIONS_RUNTIME_URL -u ACTIONS_CACHE_URL -u ACTIONS_RESULTS_URL node packages/enriched-markdown-android/display-benchmark/tools/compare.mjs --head ${{ needs.resolve.outputs.head }} --base ${{ needs.resolve.outputs.base }} --output "$RUNNER_TEMP/android-benchmarks"
- name: Upload the results
if: ${{ !cancelled() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: android-benchmarks
path: ${{ runner.temp }}/android-benchmarks
if-no-files-found: ignore
# Kept apart so pull request code never holds a write token. A cancelled run
# was superseded, so it leaves the report to the newer one.
report:
needs: [resolve, benchmark]
if: ${{ !cancelled() && needs.resolve.result == 'success' && needs.benchmark.result != 'cancelled' }}
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
pull-requests: write
steps:
# The results came from pull request code, so main's script reads them as
# data only.
- name: Checkout the report script
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
ref: ${{ needs.resolve.outputs.base }}
path: tooling
persist-credentials: false
sparse-checkout: |
.nvmrc
packages/enriched-markdown-android/display-benchmark/tools
sparse-checkout-cone-mode: false
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: tooling/.nvmrc
- name: Download the results
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: android-benchmarks
path: results
- name: Make the table
id: table
if: needs.benchmark.result == 'success'
continue-on-error: true
run: node tooling/packages/enriched-markdown-android/display-benchmark/tools/compare.mjs --report results --markdown table.md
- name: Comment on the pull request
env:
BENCHMARK: ${{ needs.benchmark.result }}
TABLE: ${{ steps.table.outcome }}
GH_TOKEN: ${{ github.token }}
NUMBER: ${{ needs.resolve.outputs.number }}
HEAD: ${{ needs.resolve.outputs.head }}
BASE: ${{ needs.resolve.outputs.base }}
BEHIND: ${{ needs.resolve.outputs.behind }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
COMMIT_URL: ${{ github.server_url }}/${{ github.repository }}/commit
run: |
set -euo pipefail
{
echo "### Android display benchmark"
echo
echo "**head** #$NUMBER at [\`${HEAD:0:10}\`]($COMMIT_URL/$HEAD) · **base** \`main\` at [\`${BASE:0:10}\`]($COMMIT_URL/$BASE)"
echo
echo "> [!IMPORTANT]"
echo "> Measured on an emulator on a shared CI runner, so the numbers only show the direction of a change. Confirm them on a physical device before relying on them; see the [display benchmark README](https://github.com/$GITHUB_REPOSITORY/blob/main/packages/enriched-markdown-android/display-benchmark/README.md#running)."
echo
if [[ -n "$BEHIND" && "$BEHIND" != "0" ]]; then
echo "The pull request is $BEHIND commit(s) behind \`main\`, so the difference also includes what \`main\` gained since. Rebase it to measure its own changes alone."
echo
fi
if [[ "$BENCHMARK" != success ]]; then
echo "The benchmark did not finish, so there are no results to compare."
elif [[ "$TABLE" != success ]]; then
echo "The benchmark finished, but its results could not be read; see the run."
else
cat table.md
fi
echo
echo "[Run]($RUN_URL)"
} > comment.md
cat comment.md >> "$GITHUB_STEP_SUMMARY"
gh pr comment "$NUMBER" --repo "$GITHUB_REPOSITORY" --body-file comment.md