Skip to content

Commit a7bb8a9

Browse files
committed
Build/Test Tools: Retry failed and stalled downloads in CI setup.
From October 15 to 28, 13 of 523 trunk Performance Tests runs needed a rerun because a setup step failed, and three Playwright installs hung for six hours. Add two composite actions. `download-with-retry` downloads with curl, retrying on any error and on stalled transfers. `run-with-retry` runs a command up to three times. Use them for the Gutenberg download in E2E tests and the WordPress Importer and mock data downloads in performance tests. Also use them for the WordPress downloads in the installation, upgrade, and performance baseline steps. Retry the global npm install in the `setup-npm` action. Also cap the Playwright browser install and the mock data import at 15 minutes each, and skip archiving performance artifacts when the tests never ran. Developed in: #15210 Props lucatume, johnbillion. See #65845. git-svn-id: https://develop.svn.wordpress.org/trunk@65266 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 985cab8 commit a7bb8a9

11 files changed

Lines changed: 129 additions & 18 deletions
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: 'Download with retry'
2+
description: 'Downloads a file with curl, retrying on any error.'
3+
4+
inputs:
5+
url:
6+
description: 'The URL to download.'
7+
required: true
8+
output:
9+
description: 'The path to save the file to, relative to the workspace.'
10+
required: true
11+
12+
runs:
13+
using: 'composite'
14+
steps:
15+
- name: Download the file
16+
shell: bash
17+
# A stalled attempt (connecting for over 30 s, or under 1 KB/s for 30 s) fails and is
18+
# retried: curl has no transfer timeout, and `timeout-minutes` would end the step unretried.
19+
# No retry starts after 10 minutes, so a long `Retry-After` can't stretch the waits.
20+
run: curl --fail --no-progress-meter --location --retry 5 --retry-all-errors --retry-max-time 600 --connect-timeout 30 --speed-limit 1024 --speed-time 30 --output "$OUTPUT" "$URL"
21+
env:
22+
URL: ${{ inputs.url }}
23+
OUTPUT: ${{ inputs.output }}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: 'Run with retry'
2+
description: 'Runs a Bash command, retrying it when it fails. For downloads curl can retry itself, use download-with-retry.'
3+
4+
inputs:
5+
command:
6+
description: 'The Bash command to run. It runs as code: read values from the `env` of the calling step, not from expressions.'
7+
required: true
8+
9+
runs:
10+
using: 'composite'
11+
steps:
12+
- name: Run the command
13+
shell: bash
14+
# Waits 10 s, then 20 s, between attempts, like `tools/local-env/scripts/utils.js`.
15+
run: |
16+
for (( attempt = 1; attempt <= MAX_ATTEMPTS; attempt++ )); do
17+
if bash -eo pipefail -c "$COMMAND"; then
18+
exit 0
19+
fi
20+
echo "::warning::Command failed on attempt $attempt of $MAX_ATTEMPTS."
21+
if (( attempt < MAX_ATTEMPTS )); then
22+
sleep $(( attempt * 10 ))
23+
fi
24+
done
25+
exit 1
26+
env:
27+
COMMAND: ${{ inputs.command }}
28+
MAX_ATTEMPTS: 3

‎.github/actions/setup-npm/action.yml‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,22 @@ runs:
1818
id: npm
1919
env:
2020
WORKING_DIRECTORY: ${{ inputs.working-directory }}
21+
NPM_INSTALL_MAX_ATTEMPTS: 3
2122
run: |
2223
NPM_VERSION="$(jq -r '.devEngines.packageManager.version // empty' package.json)"
2324
if [ -n "$NPM_VERSION" ]; then
2425
# Contributors need the floor; CI stays on that major's newest release.
25-
npm install --global "npm@${NPM_VERSION/>=/^}"
26+
# The registry can reset the connection mid-download (ECONNRESET), so retry.
27+
for (( attempt = 1; attempt <= NPM_INSTALL_MAX_ATTEMPTS; attempt++ )); do
28+
if npm install --global "npm@${NPM_VERSION/>=/^}"; then
29+
break
30+
fi
31+
echo "::warning::npm install failed on attempt $attempt of $NPM_INSTALL_MAX_ATTEMPTS."
32+
if (( attempt == NPM_INSTALL_MAX_ATTEMPTS )); then
33+
exit 1
34+
fi
35+
sleep $(( attempt * 10 ))
36+
done
2637
echo "Required npm version: $NPM_VERSION"
2738
else
2839
echo "No devEngines.packageManager.version, keeping the bundled npm."

‎.github/workflows/end-to-end-tests.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ on:
4444
- 'tools/vendors/**'
4545
- 'tools/webpack/**'
4646
- '.github/actions/setup-npm/**'
47+
- '.github/actions/download-with-retry/**'
4748
- '.github/workflows/end-to-end-tests.yml'
4849
- '.github/workflows/reusable-end-to-end-tests*.yml'
4950
workflow_dispatch:

‎.github/workflows/install-testing.yml‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ on:
1313
# Always test the workflow after it's updated.
1414
paths:
1515
- '.github/actions/setup-wp-cli/**'
16+
- '.github/actions/run-with-retry/**'
1617
- '.github/actions/smoke-check/**'
1718
- '.github/workflows/install-testing.yml'
1819
- '.version-support-*.json'
@@ -21,6 +22,7 @@ on:
2122
# Always test the workflow when changes are suggested.
2223
paths:
2324
- '.github/actions/setup-wp-cli/**'
25+
- '.github/actions/run-with-retry/**'
2426
- '.github/actions/smoke-check/**'
2527
- '.version-support-*.json'
2628
- '.github/workflows/install-testing.yml'
@@ -143,11 +145,15 @@ jobs:
143145
# WP-CLI 2.x extracts tarballs with PharData, which truncates paths longer than 100 characters.
144146
# Zip archives are not affected, so download the zip for every version.
145147
- name: Download WordPress
146-
run: |
147-
case "${WP_VERSION}" in
148-
nightly|trunk) wp core download --version=nightly ;;
149-
*) wp core download "https://wordpress.org/wordpress-${WP_VERSION}.zip" ;;
150-
esac
148+
# WP-CLI requests WordPress from WordPress.org once, so a network error fails the job.
149+
# It extracts only after a complete download, so a retry starts from an empty directory.
150+
uses: $/.github/actions/run-with-retry
151+
with:
152+
command: |
153+
case "${WP_VERSION}" in
154+
nightly|trunk) wp core download --version=nightly ;;
155+
*) wp core download "https://wordpress.org/wordpress-${WP_VERSION}.zip" ;;
156+
esac
151157
env:
152158
WP_VERSION: ${{ inputs.wp-version || 'nightly' }}
153159

‎.github/workflows/performance.yml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ on:
4444
- 'tools/vendors/**'
4545
- 'tools/webpack/**'
4646
- '.github/actions/setup-npm/**'
47+
- '.github/actions/download-with-retry/**'
4748
- '.github/actions/setup-wp-cli/**'
49+
- '.github/actions/run-with-retry/**'
4850
- '.github/workflows/performance.yml'
4951
- '.github/workflows/reusable-performance-*.yml'
5052
workflow_dispatch:

‎.github/workflows/reusable-end-to-end-tests.yml‎

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,14 +136,22 @@ jobs:
136136
LOCAL_SCRIPT_DEBUG: ${{ inputs.LOCAL_SCRIPT_DEBUG }}
137137
run: npm run env:install
138138

139+
- name: Download Gutenberg
140+
if: ${{ inputs.install-gutenberg }}
141+
# Downloaded on the runner so failed downloads are retried: WP-CLI tries once.
142+
# Release branches run this workflow from trunk: `$/` resolves to this workflow's commit, not the caller's.
143+
uses: $/.github/actions/download-with-retry
144+
with:
145+
url: https://downloads.wordpress.org/plugin/gutenberg.${{ inputs.gutenberg-version || 'latest-stable' }}.zip
146+
output: gutenberg.zip
147+
139148
- name: Install Gutenberg
140149
if: ${{ inputs.install-gutenberg }}
141150
run: |
142-
npm run env:cli -- plugin install gutenberg \
143-
${{ inputs.gutenberg-version && '--version="${GUTENBERG_VERSION}"' || '' }} \
144-
--path="/var/www/${LOCAL_DIR}"
145-
env:
146-
GUTENBERG_VERSION: ${{ inputs.gutenberg-version }}
151+
npm run env:cli -- plugin install gutenberg.zip --path="/var/www/${LOCAL_DIR}"
152+
rm gutenberg.zip
153+
# Installing from a zip doesn't log the version; unpinned branches get the latest.
154+
npm run env:cli -- plugin get gutenberg --field=version --path="/var/www/${LOCAL_DIR}"
147155
148156
- name: Install additional languages
149157
run: |

‎.github/workflows/reusable-performance-test-v2.yml‎

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ jobs:
159159
run: npm ci
160160

161161
- name: Install Playwright browsers
162+
# A hung install would otherwise run until the 6 hour job limit. The cap
163+
# leaves room for slow downloads.
164+
timeout-minutes: 15
162165
run: npm exec --no -- playwright install --with-deps chromium
163166

164167
- name: Start Docker environment
@@ -202,9 +205,11 @@ jobs:
202205
203206
- name: Set the environment to the baseline version
204207
if: ${{ inputs.subject == 'base' }}
205-
run: |
206-
VERSION="${BASE_TAG%.0}"
207-
wp core download --version="$VERSION" --force --path="${LOCAL_DIR}"
208+
# WP-CLI requests the release from WordPress.org once, so a network error fails the job.
209+
# It extracts only after a complete download, and `--force` lets a retry overwrite files.
210+
uses: $/.github/actions/run-with-retry
211+
with:
212+
command: wp core download --version="${BASE_TAG%.0}" --force --path="${LOCAL_DIR}"
208213

209214
- name: Install object cache drop-in
210215
if: ${{ inputs.memcached }}
@@ -236,12 +241,29 @@ jobs:
236241
npm run env:cli -- theme enable twentytwentyfour --network --path="/var/www/${LOCAL_DIR}"
237242
npm run env:cli -- theme enable twentytwentyfive --network --path="/var/www/${LOCAL_DIR}"
238243
244+
- name: Download WordPress Importer plugin
245+
# Downloaded on the runner so failed downloads are retried: WP-CLI tries once.
246+
uses: $/.github/actions/download-with-retry
247+
with:
248+
url: https://downloads.wordpress.org/plugin/wordpress-importer.latest-stable.zip
249+
output: wordpress-importer.zip
250+
239251
- name: Install WordPress Importer plugin
240-
run: npm run env:cli -- plugin install wordpress-importer --activate --path="/var/www/${LOCAL_DIR}"
252+
run: |
253+
npm run env:cli -- plugin install wordpress-importer.zip --activate --path="/var/www/${LOCAL_DIR}"
254+
rm wordpress-importer.zip
255+
256+
- name: Download mock data
257+
uses: $/.github/actions/download-with-retry
258+
with:
259+
url: https://raw.githubusercontent.com/WordPress/theme-test-data/b9752e0533a5acbb876951a8cbb5bcc69a56474c/themeunittestdata.wordpress.xml
260+
output: themeunittestdata.wordpress.xml
241261

242262
- name: Import mock data
263+
# The importer waits up to 300 s per attachment download and skips the ones that
264+
# fail: the cap leaves room for a stalled attachment, not a hung import.
265+
timeout-minutes: 15
243266
run: |
244-
curl -O https://raw.githubusercontent.com/WordPress/theme-test-data/b9752e0533a5acbb876951a8cbb5bcc69a56474c/themeunittestdata.wordpress.xml
245267
npm run env:cli -- import themeunittestdata.wordpress.xml --authors=create --path="/var/www/${LOCAL_DIR}"
246268
rm themeunittestdata.wordpress.xml
247269
@@ -271,13 +293,15 @@ jobs:
271293
cp ./tests/performance/wp-content/mu-plugins/server-timing.php "./${LOCAL_DIR}/wp-content/mu-plugins/server-timing.php"
272294
273295
- name: Run performance tests
296+
id: performance-tests
274297
run: npm run test:performance
275298
env:
276299
TEST_RESULTS_PREFIX: ${{ inputs.subject != 'current' && inputs.subject || '' }}
277300

278301
- name: Archive artifacts
279302
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
280-
if: always()
303+
# Without test results, a failed setup step would report a second failure here.
304+
if: ${{ always() && steps.performance-tests.outcome != 'skipped' }}
281305
with:
282306
name: performance-${{ inputs.multisite && 'multisite' || 'single' }}-${{ inputs.memcached && 'memcached' || 'default' }}-${{ inputs.subject }}
283307
path: artifacts

‎.github/workflows/reusable-upgrade-testing.yml‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,11 @@ jobs:
9595
uses: $/.github/actions/setup-wp-cli
9696

9797
- name: Download WordPress ${{ inputs.wp }}
98-
run: wp core download --version="${WP_VERSION}"
98+
# WP-CLI requests the release from WordPress.org once, so a network error fails the job.
99+
# It extracts only after a complete download, so a retry starts from an empty directory.
100+
uses: $/.github/actions/run-with-retry
101+
with:
102+
command: wp core download --version="${WP_VERSION}"
99103
env:
100104
WP_VERSION: ${{ inputs.wp }}
101105

‎.github/workflows/upgrade-develop-testing.yml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ on:
1515
- '*.php' # PHP files in the root, like wp-config-sample.php.
1616
# Confirm any changes to relevant workflow files.
1717
- '.github/actions/setup-wp-cli/**'
18+
- '.github/actions/run-with-retry/**'
1819
- '.github/actions/smoke-check/**'
1920
- '.github/actions/setup-npm/**'
2021
- '.github/workflows/upgrade-develop-testing.yml'
@@ -31,6 +32,7 @@ on:
3132
# Confirm any changes to relevant workflow files.
3233
- '.github/actions/setup-npm/**'
3334
- '.github/actions/setup-wp-cli/**'
35+
- '.github/actions/run-with-retry/**'
3436
- '.github/workflows/upgrade-develop-testing.yml'
3537
- '.github/workflows/reusable-upgrade-testing.yml'
3638
schedule:

0 commit comments

Comments
 (0)