Skip to content

Can we get a core dump? #12

Can we get a core dump?

Can we get a core dump? #12

Workflow file for this run

name: Tests
on:
pull_request:
push:
branches: ['trunk', '*/branch-*']
concurrency:
# Trunk runs need to not be cancelled for concurrency, mainly for code coverage. Everything else can be.
group: tests-${{ github.event_name }}-${{ github.ref }}-${{ case( github.event_name == 'push' && github.ref == 'refs/heads/trunk', github.run_id, '' ) }}
cancel-in-progress: true
permissions:
# actions/checkout, actions/upload-artifact, actions/download-artifact
contents: read
# Note posting the coverage comment uses an app token, so no need for permissions
env:
COMPOSER_ROOT_VERSION: 'dev-trunk'
jobs:
create-matrix:
name: 'Determine tests matrix'
runs-on: ubuntu-latest
timeout-minutes: 2 # 2025-11-20: Takes a few seconds.
outputs:
matrix: ${{ steps.create-matrix.outputs.matrix }}
steps:
- uses: actions/checkout@v6
- id: create-matrix
run: |
MATRIX="$(.github/files/generate-ci-matrix.php)"
echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT"
run-tests:
name: ${{ matrix.name }}
runs-on: ${{ matrix.runner }}
needs: create-matrix
services:
database:
image: mariadb:12.0
env:
MARIADB_ROOT_PASSWORD: root
ports:
- 3306:3306
options: --health-cmd="healthcheck.sh --su-mysql --connect --innodb_initialized" --health-interval=10s --health-timeout=5s --health-retries=5
continue-on-error: ${{ matrix.experimental }}
timeout-minutes: ${{ matrix.timeout }}
env:
TEST_SCRIPT: ${{ matrix.script }}
WP_BRANCH: ${{ matrix.wp }}
PHP_VERSION: ${{ matrix.php }}
NODE_VERSION: ${{ matrix.node }}
MONOREPO_BASE: ${{ github.workspace }}
WITH_WOOCOMMERCE: ${{ matrix.with-woocommerce }}
WITH_WPCOMSH: ${{ matrix.with-wpcomsh }}
strategy:
fail-fast: false
matrix:
include: ${{ fromJson( needs.create-matrix.outputs.matrix ) }}
# Note matrix-job outputs are kind of weird. Last-to-run job that sets a non-empty value wins.
outputs:
did-coverage: ${{ case( steps.run-tests.conclusion != 'cancelled' && steps.process-coverage.conclusion == 'success' && steps.upload-artifacts.conclusion == 'success', 'true', '' ) }}
coverage-status: ${{ case( matrix.script == 'test-coverage', steps.run-tests.conclusion, '' ) }}
steps:
- uses: actions/checkout@v6
with:
# Test coverage checks require a fetch depth > 1.
fetch-depth: 2
# For pull requests, list-changed-projects.sh needs the merge base.
# But it doesn't have to be checked out.
- name: Deepen to merge base
if: github.event_name == 'pull_request'
uses: ./.github/actions/deepen-to-merge-base
with:
checkout: false
- name: Setup tools
uses: ./.github/actions/tool-setup
with:
php: ${{ matrix.php }}
coverage: ${{ case( matrix.script == 'test-coverage', 'pcov', 'none' ) }}
node: ${{ matrix.node }}
- name: Monorepo install
run: |
echo "::group::Pnpm"
pnpm install
echo "::endgroup::"
- name: Detect changed projects
id: changed
run: |
CHANGED='{"plugins/crm":true}'
ANY_PLUGINS="$(jq --argjson changed "$CHANGED" -n '$changed | with_entries( select( .key | startswith( "plugins/" ) ) ) | any')"
echo "projects=${CHANGED}" >> "$GITHUB_OUTPUT"
echo "any-plugins=${ANY_PLUGINS}" >> "$GITHUB_OUTPUT"
- name: Select WordPress version
if: matrix.wp != 'none'
run: .github/files/select-wordpress-tag.sh
- name: Composer Install
env:
CHANGED: ${{ steps.changed.outputs.projects }}
run: |
# If we're going to be making WorDBless use WP "nightlies", remove the relevant package from Composer's cache to get the latest version.
if [[ "$WP_BRANCH" == 'trunk' && ( "$TEST_SCRIPT" == "test-php" || "$TEST_SCRIPT" == "test-coverage" ) ]]; then
echo "::group::Clear composer cache for roots/wordpress"
DIR=$(composer config cache-files-dir)
rm -rf "$DIR/roots/wordpress" "$DIR/roots/wordpress-no-content"
echo "::endgroup::"
fi
echo "::group::Composer"
composer install --working-dir=tools/php-test-env
if [[ ( "$TEST_SCRIPT" == "test-php" || "$TEST_SCRIPT" == "test-coverage" ) && ( "$WP_BRANCH" == 'trunk' || "$WP_BRANCH" == 'previous' ) ]]; then
VER=$(composer --format=json --working-dir="tools/php-test-env" show | jq -r '.installed[] | select( .name == "roots/wordpress" ) | .version')
if [[ -n "$VER" ]]; then
INSVER=$WORDPRESS_TAG
[[ "$WORDPRESS_TAG" == 'trunk' ]] && INSVER="dev-main as $VER"
echo "Supposed to run tests against WordPress $WORDPRESS_TAG, so setting roots/wordpress and roots/wordpress-no-content to \"$INSVER\""
# Composer seems to sometimes have issues with deleting the wordpress dir on its own, so do it manually first.
rm -rf "tools/php-test-env/wordpress"
composer --working-dir="tools/php-test-env" require --dev "roots/wordpress:$INSVER" "roots/wordpress-no-content:$INSVER"
fi
fi
echo "::endgroup::"
echo "Checking for non-mirrored require-dev packages, in case this is testing a release branch"
for SLUG in $( jq -r 'keys[]' <<<"$CHANGED" ); do
[[ "$SLUG" == "monorepo" ]] && continue
PKGS=()
readarray -t PKGS < <( jq -r '.extra["non-mirrored-require-dev"] // empty | .[] | . += "=@dev"' "projects/$SLUG/composer.json" )
if [[ ${#PKGS[@]} -gt 0 ]]; then
echo "::group::Adding packages for $SLUG: ${PKGS[*]}"
# Make sure monorepo repositories entry is present.
JSON=$( jq --tab '.repositories //= [] | if any( .repositories[]; .type == "path" and ( .url | startswith( "../" ) ) and .options?.monorepo? ) then . else .repositories += [ { type: "path", url: "../../packages/*", options: { monorepo: true } } ] end' "projects/$SLUG/composer.json" )
echo "$JSON" > "projects/$SLUG/composer.json"
# Use --no-install and --ignore-platform-reqs here. Code below (either in .github/files/setup-wordpress-env.sh or the "Run project tests" step) will do a `composer install` or `composer update` as necessary.
composer require --working-dir="projects/$SLUG/" --dev --no-install --ignore-platform-reqs "${PKGS[@]}"
echo "::endgroup::"
fi
done
- name: Setup WordPress environment for plugin tests
env:
API_TOKEN_GITHUB: ${{ secrets.GITHUB_TOKEN }}
CHANGED: ${{ steps.changed.outputs.projects }}
if: steps.changed.outputs.any-plugins == 'true' && matrix.wp != 'none'
run: .github/files/setup-wordpress-env.sh
- name: Run project tests
id: run-tests
env:
FORCE_PACKAGE_TESTS: ${{ case( matrix.force-package-tests, 'true', 'false' ) }}
CHANGED: ${{ steps.changed.outputs.projects }}
run: |
EXIT=0
declare -A PIDS
PIDS=()
MAXPIDS=$( nproc )
FAILED=()
mkdir artifacts
[[ "$TEST_SCRIPT" == "test-coverage" ]] && mkdir coverage
for P in composer.json projects/*/*/composer.json; do
if [[ ${#PIDS[@]} -ge $MAXPIDS ]]; then
if ! wait -fn -p PID "${!PIDS[@]}"; then
echo "::error::Tests for ${PIDS[$PID]} failed!"
FAILED+=( "${PIDS[$PID]}" )
EXIT=1
fi
echo "Finished ${PIDS[$PID]}"
unset PIDS[$PID]
fi
if [[ "$P" == "composer.json" ]]; then
DIR="."
SLUG="monorepo"
else
DIR="${P%/composer.json}"
SLUG="${DIR#projects/}"
fi
if [[ "${SLUG%%/*}" != "plugins" && "$WP_BRANCH" != 'latest' && "$WP_BRANCH" != 'none' && "$FORCE_PACKAGE_TESTS" != "true" ]]; then
echo "Skipping $SLUG, only plugins run for WP_BRANCH = $WP_BRANCH"
continue
fi
if ! jq --argjson changed "$CHANGED" --arg p "$SLUG" -ne '$changed[$p] // false' > /dev/null; then
echo "Skipping $SLUG, no changes in it or its dependencies"
elif ! jq --arg script "$TEST_SCRIPT" -e '.scripts[$script] // false' "$P" > /dev/null; then
echo "Skipping $SLUG, no test script is defined in composer.json"
elif php -r 'exit( preg_match( "/^>=\\s*(\\d+\\.\\d+)$/", $argv[1], $m ) && version_compare( PHP_VERSION, $m[1], "<" ) ? 0 : 1 );' "$( jq -r '.require.php // ""' "$P" )"; then
echo "Skipping $SLUG, requires PHP $( jq -r '.require.php // ""' "$P" ) but PHP version is $( php -r 'echo PHP_VERSION;' )"
else
if jq --arg script "skip-$TEST_SCRIPT" -e '.scripts[$script] // false' "$P" > /dev/null; then
{ composer --working-dir="$DIR" run "skip-$TEST_SCRIPT"; CODE=$?; } || true
if [[ $CODE -eq 3 ]]; then
echo "Skipping tests for $SLUG due to skip-$TEST_SCRIPT script"
continue
elif [[ $CODE -ne 0 ]]; then
echo "::error::Script skip-$TEST_SCRIPT failed to run $CODE!"
FAILED+=( "$SLUG" )
EXIT=1
continue
fi
fi
echo "Running tests for $SLUG"
{
# Composer install, if appropriate. Note setup-wordpress-env.sh did it already for plugins.
if [[ "${SLUG%%/*}" != "plugins" && ( "$TEST_SCRIPT" == "test-php" || "$TEST_SCRIPT" == "test-coverage" ) ]]; then
if [[ "$TEST_SCRIPT" == "test-coverage" ]] &&
! jq -e '.scripts["test-php"]' "$DIR/composer.json" &>/dev/null
then
echo "Skipping composer install, assuming test-coverage is only JS because the project has no test-php."
else
if [[ ! -f "$DIR/composer.lock" ]]; then
echo 'No composer.lock, running `composer update`'
composer --working-dir="$DIR" update
elif composer --working-dir="$DIR" check-platform-reqs --lock; then
echo 'Platform reqs pass, running `composer install`'
composer --working-dir="$DIR" install
if [[ "$TEST_SCRIPT" == "test-php" ]] && composer info --locked phpunit/phpunit &>/dev/null; then
echo 'Updating PHPUnit in case a newer version than locked is usable'
composer --working-dir="$DIR" update -W phpunit/phpunit
fi
else
echo 'Platform reqs failed, running `composer update`'
composer --working-dir="$DIR" update
fi
fi
fi
if [[ "${SLUG%%/*}" == "plugins" ]]; then
export WP_TESTS_CONFIG_FILE_PATH="$WORDPRESS_DEVELOP_DIR/wp-tests-config.${SLUG##*/}.php"
fi
mkdir -p "artifacts/$SLUG"
export ARTIFACTS_DIR="$GITHUB_WORKSPACE/artifacts/$SLUG"
if [[ "$TEST_SCRIPT" == "test-coverage" ]]; then
mkdir -p "coverage/$SLUG"
export COVERAGE_DIR="$GITHUB_WORKSPACE/coverage/$SLUG"
fi
ulimit -c unlimited
ulimit -c
echo "$ARTIFACTS_DIR/core" | sudo tee /proc/sys/kernel/core_pattern
FAIL=false
if ! composer run --timeout=0 --working-dir="$DIR" "$TEST_SCRIPT"; then
FAIL=true
fi
# Actions seems to slow down if there are a lot of files, so clean up Composer stuff after each test.
# We don't do it for JS stuff, as that might break things with how JS does package deps.
rm -rf "$DIR/vendor" "$DIR/jetpack_vendor" "$DIR/wordpress"
if $FAIL; then
echo "Tests for $SLUG failed!"
exit 1
fi
} 2> >( sed -u 's!^!['"$SLUG"'] !' >&2 ) > >( sed -u 's!^!['"$SLUG"'] !' ) &
PIDS[$!]=$SLUG
fi
done
while [[ ${#PIDS[@]} -gt 0 ]]; do
if ! wait -fn -p PID "${!PIDS[@]}"; then
echo "::error::Tests for ${PIDS[$PID]} failed!"
FAILED+=( "${PIDS[$PID]}" )
EXIT=1
fi
echo "Finished ${PIDS[$PID]}"
unset PIDS[$PID]
done
if [[ ${#FAILED[@]} -gt 0 ]]; then
echo ''
echo 'The following tests failed:'
printf " - %s\n" "${FAILED[@]}"
fi
exit $EXIT
- name: Process coverage results
id: process-coverage
env:
CHANGED: ${{ steps.changed.outputs.projects }}
if: matrix.script == 'test-coverage' && always()
run: .github/files/coverage-munger/process-coverage.sh
- name: Check for artifacts
id: check-artifacts
# Default for `if` is `success()`, we want this to run always.
if: always()
run: |
[[ -d artifacts ]] && find artifacts -type d -empty -delete
if [[ -d artifacts ]]; then
echo "any=true" >> "$GITHUB_OUTPUT"
else
echo "any=false" >> "$GITHUB_OUTPUT"
fi
- name: Upload artifacts
id: upload-artifacts
if: always() && steps.check-artifacts.outputs.any == 'true'
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.artifact }}
path: artifacts
include-hidden-files: true
retention-days: 7