diff --git a/.github/workflows/install-test-comment.yml b/.github/workflows/install-test-comment.yml new file mode 100644 index 0000000..cc274ca --- /dev/null +++ b/.github/workflows/install-test-comment.yml @@ -0,0 +1,108 @@ +name: Install test comment + +# Posts the install test's numbers on the pull request as one comment, kept +# up to date on every run, next to quattro's latest numbers. GitHub runs +# workflow_run workflows from the default branch, with write access even for +# pull requests from forks, so this job never checks out or runs pull request +# code: it only reads the finished run's artifact, as data. + +on: + workflow_run: + workflows: ["Install test"] + types: [completed] + +permissions: + actions: read + pull-requests: write + +jobs: + comment: + if: github.event.workflow_run.event == 'pull_request' + runs-on: ubuntu-latest + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + RUN_ID: ${{ github.event.workflow_run.id }} + RUN_URL: ${{ github.event.workflow_run.html_url }} + HEAD_SHA: ${{ github.event.workflow_run.head_sha }} + CONCLUSION: ${{ github.event.workflow_run.conclusion }} + steps: + - name: Fetch this run's numbers and quattro's latest + run: | + gh run download "$RUN_ID" -R "$REPO" -D pr || true + base=$(gh run list -R "$REPO" --workflow install-test.yml --branch quattro --event push \ + --status success -L 1 --json databaseId -q '.[0].databaseId') + if [ -n "$base" ]; then gh run download "$base" -R "$REPO" -D base || true; fi + + - name: Comment on the pull request + run: | + set -euo pipefail + marker='' + + # The number comes from the pull request's own run, so trust it only + # when that pull request's head is the commit this run tested. + pr=$(find pr -name pr-number -exec cat {} + 2>/dev/null | head -1 | tr -dc 0-9) + [ -n "$pr" ] || { echo "No pull request number in the artifact"; exit 0; } + [ "$(gh api "repos/$REPO/pulls/$pr" -q .head.sha)" = "$HEAD_SHA" ] || + { echo "PR #$pr has moved past $HEAD_SHA; not commenting"; exit 0; } + + timing() { find "$1" -name omarchy-install-timing.json 2>/dev/null | head -1; } + install_s() { [ -n "$1" ] && jq -r '.finished_at - .started_at' "$1" 2>/dev/null; } + # "Startup finished in … = 1min 2.3s" or "= 12.104s" -> seconds + boot_s() { + local f + f=$(find "$1" -path "$2" 2>/dev/null | head -1) + [ -n "$f" ] && head -1 "$f" | sed -n 's/.*= *//p' | + awk '{t=0; for (i=1;i<=NF;i++) { if ($i ~ /min$/) t+=60*$i; else if ($i ~ /ms$/) t+=$i/1000; else t+=$i } printf "%.3f", t}' + } + cell() { [ -n "$1" ] && awk -v x="$1" 'BEGIN{printf "%.1f s", x}' || echo "–"; } + change() { + [ -n "$1" ] && [ -n "$2" ] || { echo "–"; return; } + awk -v a="$1" -v b="$2" 'BEGIN{d=a-b; printf "%s%.1f s", (d>=0?"+":"−"), (d<0?-d:d)}' + } + + pt=$(timing pr); bt=$(timing base) + rows=( + "Install|$(install_s "$pt")|$(install_s "$bt")" + "First boot|$(boot_s pr '*/first-boot-systemd-analyze.txt')|$(boot_s base '*/first-boot-systemd-analyze.txt')" + "Everyday boot|$(boot_s pr '*-boot/systemd-analyze.txt')|$(boot_s base '*-boot/systemd-analyze.txt')" + ) + + status="passed"; [ "$CONCLUSION" = success ] || status="**${CONCLUSION}**" + { + echo "$marker" + echo "### Install test $status" + echo + echo "| | This PR | quattro | Change |" + echo "| --- | ---: | ---: | ---: |" + for row in "${rows[@]}"; do + IFS='|' read -r name this base_v <<<"$row" + echo "| $name | $(cell "$this") | $(cell "$base_v") | $(change "$this" "$base_v") |" + done + if [ -n "$pt" ]; then + echo + echo "
Install phases" + echo + echo "| Phase | This PR | quattro |" + echo "| --- | ---: | ---: |" + # Phase names come from the pull request's installer: keep them to plain text. + jq -r --slurpfile b <( [ -n "$bt" ] && cat "$bt" || echo '{"phases":[]}' ) ' + ($b[0].phases | map({key: .name, value: .elapsed}) | from_entries) as $base + | .phases[] + | "| \(.name | gsub("[^A-Za-z0-9 +&/().-]"; "") | .[0:60]) | \(.elapsed * 10 | round / 10) s | \(($base[.name] // null) | if . == null then "–" else "\(. * 10 | round / 10) s" end) |" + ' "$pt" + echo + echo "
" + fi + echo + echo "[Run]($RUN_URL) · ${HEAD_SHA:0:8}" + } > comment.md + + existing=$(gh api "repos/$REPO/issues/$pr/comments" --paginate \ + -q ".[] | select(.user.login == \"github-actions[bot]\" and (.body | startswith(\"$marker\"))) | .id" | head -1) + if [ -n "$existing" ]; then + gh api -X PATCH "repos/$REPO/issues/comments/$existing" -F body=@comment.md >/dev/null + else + gh api "repos/$REPO/issues/$pr/comments" -F body=@comment.md >/dev/null + fi + cat comment.md diff --git a/.github/workflows/install-test.yml b/.github/workflows/install-test.yml index 623d7aa..9855536 100644 --- a/.github/workflows/install-test.yml +++ b/.github/workflows/install-test.yml @@ -7,6 +7,9 @@ name: Install test on: pull_request: + # Each merge records quattro's numbers, which PR comments compare against. + push: + branches: [quattro] workflow_dispatch: permissions: @@ -88,12 +91,19 @@ jobs: fi } >> "$GITHUB_STEP_SUMMARY" + # The comment workflow runs from quattro and reads only this artifact; + # it needs the PR number, which a workflow_run event omits for forks. + - name: Record the pull request + if: always() && github.event_name == 'pull_request' + run: mkdir -p test-runs && echo "${{ github.event.pull_request.number }}" > test-runs/pr-number + - name: Upload test artifacts if: always() uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: name: install-test-${{ github.run_id }}-${{ github.run_attempt }} path: | + test-runs/pr-number test-runs/*/omarchy-install-timing.json test-runs/*/omarchy-install.log test-runs/*/first-boot-systemd-analyze.txt