-
Notifications
You must be signed in to change notification settings - Fork 83
203 lines (179 loc) · 8.35 KB
/
Copy pathcorpus-diff.yml
File metadata and controls
203 lines (179 loc) · 8.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# Parses a pinned corpus of WordPress core source with the parser at the base
# branch and with the PR merged into it, and diffs the two JSON exports.
# Anything in the diff is a behavior change this PR makes: every hunk must be
# either intended (and explained in the PR) or a regression.
#
# Policy decisions, deliberate:
# - The corpus is pinned in `.github/corpus-version` to one WordPress tag so
# diffs are reproducible.
# - The head checkout's tools/export-corpus.php drives both sides, so tooling
# changes never masquerade as parser changes.
# - The exports are diffed as emitted, without prep-diff.php. Both sides share
# the corpus, the PHP binary, and the exporter, so the export is already
# deterministic; prep-diff.php exists to reconcile exports from different
# environments, and its erasures (line numbers, global-namespace prefixes)
# and collection sorting would hide or scatter real changes here.
# - Non-blocking: the job succeeds even when the diff is non-empty. The diff
# is published as an artifact, summarized, and posted as a PR comment that
# is updated in place on every run. Make it blocking only after the signal
# has proven trustworthy.
# - The comment needs a token that can write to the PR. pull_request runs for
# forks and for Dependabot get a read-only token, so those PRs get the
# summary and the artifact only.
# - PHP 8.4, the newest runtime in the unit-test matrix. Both sides run under
# the same binary, so the PHP version never shows up as a parser change.
name: Corpus Diff
on:
pull_request:
# A new push to the PR supersedes any run still in flight for it.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions: {}
jobs:
corpus-diff:
name: WordPress corpus regeneration diff
runs-on: ubuntu-latest
permissions:
contents: read
env:
CORPUS_PIN_FILE: .github/corpus-version
LC_ALL: C
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Read corpus pin
run: |
set -euo pipefail
WP_CORPUS_TAG="$(cat "${CORPUS_PIN_FILE}")"
if [[ ! "${WP_CORPUS_TAG}" =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then
echo "::error::${CORPUS_PIN_FILE} does not contain a usable WordPress version (got: '${WP_CORPUS_TAG}')."
exit 1
fi
echo "Pinned corpus tag: ${WP_CORPUS_TAG}"
echo "WP_CORPUS_TAG=${WP_CORPUS_TAG}" >> "${GITHUB_ENV}"
- name: Set up PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: "8.4"
coverage: none
# The key carries the corpus layout: bump the suffix when the download
# step changes what it keeps.
- name: Cache corpus
id: cache-corpus
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: corpus
key: wp-corpus-${{ env.WP_CORPUS_TAG }}-php-all
# The whole release: wp-admin, wp-includes, the root files, and
# wp-content with the bundled themes and plugins. Only PHP files are
# kept; the parser reads nothing else.
- name: Download corpus
if: steps.cache-corpus.outputs.cache-hit != 'true'
run: |
curl -sSfL -o wordpress.zip "https://github.com/WordPress/WordPress/archive/refs/tags/${WP_CORPUS_TAG}.zip"
unzip -q wordpress.zip
mv "WordPress-${WP_CORPUS_TAG}" corpus
find corpus -type f ! -name '*.php' -delete
rm wordpress.zip
# An empty or miscached corpus would make both exports emit `[]` and the
# diff trivially empty, a green job that checked nothing. A release has
# well over 1500 PHP files on any supported tag.
- name: Verify corpus
run: |
count=$(find corpus -name '*.php' | wc -l)
echo "Corpus PHP files: ${count}"
[ "$count" -ge 1500 ]
# On pull_request, HEAD is the PR merged into the base branch, so its
# merge base with the base branch is the base branch tip, not the commit
# the PR branched from. That is the intended comparison: base as it is
# now against base plus exactly this PR, with no hunks from other work
# that landed since the branch point.
- name: Check out base
env:
BASE_REF: ${{ github.base_ref }}
run: git worktree add base "$(git merge-base -- "origin/${BASE_REF}" HEAD)"
- name: Install Composer dependencies (head)
run: composer install --no-interaction --no-security-blocking
- name: Install Composer dependencies (base)
run: composer --working-dir=base install --no-interaction --no-security-blocking
# The size floor guards the same failure mode as Verify corpus: a real
# export is tens of megabytes of JSON.
- name: Export corpus (base)
run: |
php -d memory_limit=4G tools/export-corpus.php base corpus > base.json
ls -l base.json
[ "$(wc -c < base.json)" -ge 1000000 ]
- name: Export corpus (head)
run: |
php -d memory_limit=4G tools/export-corpus.php . corpus > head.json
ls -l head.json
[ "$(wc -c < head.json)" -ge 1000000 ]
- name: Diff
run: |
# diff exits 1 on differences (expected) and 2 on trouble (fail).
diff -u --label base --label head base.json head.json > corpus.diff || [ $? -eq 1 ]
echo "Corpus diff: $(grep -c '^@@' corpus.diff || true) hunks, $(wc -l < corpus.diff) lines"
- name: Upload diff
id: upload
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
path: corpus.diff
archive: false
if-no-files-found: ignore
# Render on failure too: the comment is updated in place, so skipping
# this step would leave the previous push's report standing as current.
- name: Render report
if: ${{ !cancelled() }}
env:
JOB_STATUS: ${{ job.status }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
ARTIFACT_URL: ${{ steps.upload.outputs.artifact-url }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
if [ "${JOB_STATUS}" = "success" ]; then
BASE_SHA="$(git -C base rev-parse HEAD)" tools/corpus-diff-comment.sh corpus.diff > comment.md
else
printf '<!-- corpus-diff -->\n### Corpus diff\n\n**Run failed** at `%s`; no diff was produced for this push. See the [run log](%s).\n' \
"${HEAD_SHA:0:7}" "${RUN_URL}" > comment.md
fi
tail -n +2 comment.md >> "$GITHUB_STEP_SUMMARY"
- name: Upload rendered report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
path: comment.md
archive: false
if-no-files-found: ignore
comment:
name: Publish corpus diff report
needs: corpus-diff
if: ${{ !cancelled() && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]' }}
runs-on: ubuntu-latest
permissions:
pull-requests: write # Create or update the corpus diff comment.
steps:
- name: Download rendered report
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: comment.md
# Create the comment on the first run and update it on every later one,
# so the PR carries one report that reflects the latest push.
- name: Comment on the pull request
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
PR: ${{ github.event.pull_request.number }}
run: |
ids=$(gh api "repos/${REPO}/issues/${PR}/comments" --paginate \
--jq '.[] | select(.user.login == "github-actions[bot]") | select(.body | startswith("<!-- corpus-diff -->")) | .id')
id=${ids%%$'\n'*}
if [ -n "$id" ]; then
gh api --silent -X PATCH "repos/${REPO}/issues/comments/${id}" -F body=@comment.md
else
gh api --silent -X POST "repos/${REPO}/issues/${PR}/comments" -F body=@comment.md
fi