Skip to content

FEATURE: Atomic events (commitAll) and simplifies locking (removes content stream closing) #8641

FEATURE: Atomic events (commitAll) and simplifies locking (removes content stream closing)

FEATURE: Atomic events (commitAll) and simplifies locking (removes content stream closing) #8641

Workflow file for this run

name: build
on:
workflow_dispatch:
push:
branches: [ '[0-9]+.[0-9]' ]
pull_request:
branches: [ '[0-9]+.[0-9]' ]
permissions: {}
env:
FLOW_CONTEXT: Testing
NEOS_TARGET_VERSION: 9.0
NEOS_DIST_FOLDER: neos-development-distribution
NEOS_FOLDER: neos-development-collection
jobs:
static-analysis:
permissions:
contents: read # to fetch code (actions/checkout)
if: "!contains(github.event.head_commit.message, '[skip ci]')"
name: "Static analysis (PHP ${{ matrix.php }})"
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ['8.4', '8.5']
defaults:
run:
working-directory: ${{ env.NEOS_DIST_FOLDER }}
steps:
# Local composite actions are resolved from the workspace root, so the
# workflow's own repository must be checked out before `prepare-neos` runs.
- name: Checkout workflow repository
uses: actions/checkout@v5
- uses: ./.github/actions/prepare-neos
with:
php-version: ${{ matrix.php }}
dependencies: ${{ matrix.dependencies || 'highest' }}
collection-path: ${{ env.NEOS_FOLDER }}
distribution-path: ${{ env.NEOS_DIST_FOLDER }}
- name: Static analysis (phpcs + phpstan + distributionintegrity)
run: |
cd Packages/Neos
composer lint
unit-tests:
permissions:
contents: read # to fetch code (actions/checkout)
if: "!contains(github.event.head_commit.message, '[skip ci]')"
name: "Unit tests (PHP ${{ matrix.php }}, deps: ${{ matrix.dependencies || 'highest' }})"
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ['8.4', '8.5']
services:
redis:
image: redis:alpine
ports:
- "6379:6379"
options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
memcached:
image: memcached:alpine
ports:
- "11211:11211"
defaults:
run:
working-directory: ${{ env.NEOS_DIST_FOLDER }}
steps:
- name: Checkout workflow repository
uses: actions/checkout@v5
- uses: ./.github/actions/prepare-neos
with:
php-version: ${{ matrix.php }}
dependencies: ${{ matrix.dependencies || 'highest' }}
collection-path: ${{ env.NEOS_FOLDER }}
distribution-path: ${{ env.NEOS_DIST_FOLDER }}
- name: Run unit tests
run: bin/phpunit --colors -c Build/BuildEssentials/PhpUnit/UnitTests.xml --verbose
functional-tests:
permissions:
contents: read # to fetch code (actions/checkout)
if: "!contains(github.event.head_commit.message, '[skip ci]')"
name: "Functional tests (PHP ${{ matrix.php }}, ${{ matrix.db.image }})"
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ['8.4', '8.5']
db:
- { engine: mysql, image: 'mariadb:10.8', port: 3306, options: '--health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3' }
- { engine: pgsql, image: 'postgres:18.3', port: 5432, options: '--health-cmd=pg_isready --health-interval=10s --health-timeout=5s --health-retries=3' }
include:
# Additional MySQL engines on the most-supported PHP version.
- { php: '8.4', db: { engine: mysql, image: 'mysql:8.0', port: 3306, options: '--health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3' } }
- { php: '8.4', db: { engine: mysql, image: 'mysql:8.4', port: 3306, options: '--health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3' } }
# Bleeding-edge engines.
- { php: '8.5', db: { engine: mysql, image: 'mariadb:12.2', port: 3306, options: '--health-cmd="healthcheck.sh --connect --innodb_initialized" --health-interval=10s --health-timeout=5s --health-retries=3' } }
- { php: '8.5', db: { engine: pgsql, image: 'postgres:18-alpine', port: 5432, options: '--health-cmd=pg_isready --health-interval=10s --health-timeout=5s --health-retries=3' } }
services:
database:
image: ${{ matrix.db.image }}
env:
MYSQL_USER: neos
MYSQL_PASSWORD: neos
MYSQL_DATABASE: flow_functional_testing
MYSQL_ROOT_PASSWORD: neos
POSTGRES_USER: neos
POSTGRES_PASSWORD: neos
POSTGRES_DB: flow_functional_testing
ports:
- "${{ matrix.db.port }}:${{ matrix.db.port }}"
options: ${{ matrix.db.options }}
redis:
image: redis:alpine
ports:
- "6379:6379"
options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
memcached:
image: memcached:alpine
ports:
- "11211:11211"
defaults:
run:
working-directory: ${{ env.NEOS_DIST_FOLDER }}
steps:
- name: Checkout workflow repository
uses: actions/checkout@v5
- uses: ./.github/actions/prepare-neos
with:
php-version: ${{ matrix.php }}
dependencies: ${{ matrix.dependencies || 'highest' }}
collection-path: ${{ env.NEOS_FOLDER }}
distribution-path: ${{ env.NEOS_DIST_FOLDER }}
- uses: ./.github/actions/configure-neos
with:
db-engine: ${{ matrix.db.engine }}
db-port: ${{ matrix.db.port }}
working-directory: ${{ env.NEOS_DIST_FOLDER }}
flow-context: ${{ env.FLOW_CONTEXT }}
- name: Run migrations to test if they work
run: FLOW_CONTEXT=Testing ./flow doctrine:migrate
- name: Run functional tests
# Postgres skips the CR-specific functional tests (CR is mysql-only in 9.0).
run: ${{ matrix.db.engine == 'pgsql' && 'SKIP_CR_POSTGRESQL_TESTS=1' || '' }} bin/phpunit --colors --stop-on-failure -c Build/BuildEssentials/PhpUnit/FunctionalTests.xml --testsuite "Neos tests" --verbose
parallel-tests:
permissions:
contents: read # to fetch code (actions/checkout)
if: "!contains(github.event.head_commit.message, '[skip ci]')"
name: "Parallel tests (PHP ${{ matrix.php }}, ${{ matrix.db.image }})"
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ['8.4', '8.5']
db:
- { engine: mysql, image: 'mariadb:10.8', port: 3306, options: '--health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3' }
# TODO: enable parallel tests also for Postgres
# - { engine: pgsql, image: 'postgres:18.3', port: 5432, options: '--health-cmd=pg_isready --health-interval=10s --health-timeout=5s --health-retries=3' }
include:
# Additional MySQL engines on the most-supported PHP version.
- { php: '8.4', db: { engine: mysql, image: 'mysql:8.0', port: 3306, options: '--health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3' } }
- { php: '8.4', db: { engine: mysql, image: 'mysql:8.4', port: 3306, options: '--health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3' } }
# Bleeding-edge engines.
- { php: '8.5', db: { engine: mysql, image: 'mariadb:12.2', port: 3306, options: '--health-cmd="healthcheck.sh --connect --innodb_initialized" --health-interval=10s --health-timeout=5s --health-retries=3' } }
services:
database:
image: ${{ matrix.db.image }}
env:
MYSQL_USER: neos
MYSQL_PASSWORD: neos
MYSQL_DATABASE: flow_functional_testing
MYSQL_ROOT_PASSWORD: neos
POSTGRES_USER: neos
POSTGRES_PASSWORD: neos
POSTGRES_DB: flow_functional_testing
POSTGRES_ROOT_PASSWORD: neos
ports:
- "${{ matrix.db.port }}:${{ matrix.db.port }}"
options: ${{ matrix.db.options }}
# Postgres behavioral tests still need a mariadb instance because the
# DoctrineDbalAdapter content graph projection only supports mysql today.
mariadb:
image: mariadb:10.8
env:
MYSQL_USER: neos
MYSQL_PASSWORD: neos
MYSQL_DATABASE: flow_functional_testing
MYSQL_ROOT_PASSWORD: neos
ports:
- "3307:3306"
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
redis:
image: redis:alpine
ports:
- "6379:6379"
options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
memcached:
image: memcached:alpine
ports:
- "11211:11211"
defaults:
run:
working-directory: ${{ env.NEOS_DIST_FOLDER }}
steps:
- name: Checkout workflow repository
uses: actions/checkout@v5
- uses: ./.github/actions/prepare-neos
with:
php-version: ${{ matrix.php }}
dependencies: ${{ matrix.dependencies || 'highest' }}
collection-path: ${{ env.NEOS_FOLDER }}
distribution-path: ${{ env.NEOS_DIST_FOLDER }}
- uses: ./.github/actions/configure-behavioral-tests
with:
working-directory: ${{ env.NEOS_DIST_FOLDER }}
- name: Run Parallel Tests for the ES CR
id: paralleltests
timeout-minutes: 30
continue-on-error: true
run: |
cd Packages/Neos
composer test:parallel
- name: Fail pipeline if Parallel Tests broke
if: matrix.db.engine == 'mysql' && steps.paralleltests.outcome != 'success'
shell: bash
run: |
cd Packages/Neos
cat Neos.ContentRepository.BehavioralTests/Tests/Parallel/log.txt
echo "\n\nDumping last 100 events\n\n"
mysql -u neos -pneos -h 127.0.0.1 -P ${{ matrix.db.port }} flow_functional_testing --table < <(echo "SELECT * FROM cr_test_parallel_events ORDER BY sequenceNumber DESC LIMIT 100")
exit 1
behavioral-tests:
permissions:
contents: read # to fetch code (actions/checkout)
if: "!contains(github.event.head_commit.message, '[skip ci]')"
name: "Behavioral tests (PHP ${{ matrix.php }}, ${{ matrix.db.image }})"
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ['8.4', '8.5']
db:
- { engine: mysql, image: 'mariadb:10.8', port: 3306, options: '--health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3' }
include:
# Additional MySQL engines on the most-supported PHP version.
- { php: '8.4', db: { engine: mysql, image: 'mysql:8.0', port: 3306, options: '--health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3' } }
- { php: '8.4', db: { engine: mysql, image: 'mysql:8.4', port: 3306, options: '--health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3' } }
- { php: '8.4', db: { engine: pgsql, image: 'postgres:18.3', port: 5432, options: '--health-cmd=pg_isready --health-interval=10s --health-timeout=5s --health-retries=3' } }
# Bleeding-edge engines.
- { php: '8.5', db: { engine: mysql, image: 'mariadb:12.2', port: 3306, options: '--health-cmd="healthcheck.sh --connect --innodb_initialized" --health-interval=10s --health-timeout=5s --health-retries=3' } }
- { php: '8.5', db: { engine: pgsql, image: 'postgres:18-alpine', port: 5432, options: '--health-cmd=pg_isready --health-interval=10s --health-timeout=5s --health-retries=3' } }
services:
database:
image: ${{ matrix.db.image }}
env:
MYSQL_USER: neos
MYSQL_PASSWORD: neos
MYSQL_DATABASE: flow_functional_testing
MYSQL_ROOT_PASSWORD: neos
POSTGRES_USER: neos
POSTGRES_PASSWORD: neos
POSTGRES_DB: flow_functional_testing
POSTGRES_ROOT_PASSWORD: neos
ports:
- "${{ matrix.db.port }}:${{ matrix.db.port }}"
options: ${{ matrix.db.options }}
# Postgres behavioral tests still need a mariadb instance because the
# DoctrineDbalAdapter content graph projection only supports mysql today.
mariadb:
image: mariadb:10.8
env:
MYSQL_USER: neos
MYSQL_PASSWORD: neos
MYSQL_DATABASE: flow_functional_testing
MYSQL_ROOT_PASSWORD: neos
ports:
- "3307:3306"
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
redis:
image: redis:alpine
ports:
- "6379:6379"
options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
memcached:
image: memcached:alpine
ports:
- "11211:11211"
defaults:
run:
working-directory: ${{ env.NEOS_DIST_FOLDER }}
steps:
- name: Checkout workflow repository
uses: actions/checkout@v5
- uses: ./.github/actions/prepare-neos
with:
php-version: ${{ matrix.php }}
dependencies: ${{ matrix.dependencies || 'highest' }}
collection-path: ${{ env.NEOS_FOLDER }}
distribution-path: ${{ env.NEOS_DIST_FOLDER }}
- uses: ./.github/actions/configure-behavioral-tests
with:
working-directory: ${{ env.NEOS_DIST_FOLDER }}
- name: Run Behavioral Tests (ES CR && Neos.Neos)
id: escrtests
# Continue on error so we always reach the DB-dump upload step below.
continue-on-error: true
run: |
mkdir -p ${{ github.workspace }}/Data/DebugDatabaseDumps
touch ${{ github.workspace }}/Data/DebugDatabaseDumps/keep
export FLOW_CONTEXT=${{ matrix.db.engine == 'pgsql' && 'Testing/Behat/Postgres' || 'Testing/Behat' }}
./flow package:list --loading-order
./flow doctrine:migrate --quiet
cd Packages/Neos
# Don't exit early — we want to reach the DB-dump step on failure.
set +e
(
set -e
composer test:behavioral -- -vvv --stop-on-failure
${{ matrix.db.engine == 'mysql' && 'composer run test:behavioral-maria-mysql -- -vvv --stop-on-failure' || 'true' }}
)
retVal=$?
# automatically search for race conditions
cd ../../
./flow raceConditionTracker:analyzeTrace --store-trace ${{ github.workspace }}/Data/DebugDatabaseDumps/race-trace.ndjson || true
set -e
if [ $retVal -ne 0 ]; then
echo "DUMPING POSTGRES AND MYSQL DATABASE (because of error)"
echo "copying logs"
cp -R Data/Logs ${{ github.workspace }}/Data/DebugDatabaseDumps/Logs
sudo DEBIAN_FRONTEND=noninteractive apt -y remove mysql-client libmysqlclient-dev mysql-server
sudo DEBIAN_FRONTEND=noninteractive apt -y autoremove
sudo DEBIAN_FRONTEND=noninteractive apt -y autoclean
sudo apt-get update
sudo apt-get install -y postgresql-client mariadb-client
echo "writing PW file"
touch ~/.pgpass
echo "127.0.0.1:5432:flow_functional_testing:neos:neos" > ~/.pgpass
chmod 600 ~/.pgpass
if [ "${{ matrix.db.engine }}" == "pgsql" ]; then
echo "starting pg_dump"
pg_dump --file=${{ github.workspace }}/Data/DebugDatabaseDumps/pg_dump.sql --format=plain --no-owner --dbname=flow_functional_testing --host=127.0.0.1 --username=neos --no-password
echo "pg_dump finished"
fi
echo "starting mysqldump"
mysqldump --force --host=127.0.0.1 -u neos -pneos flow_functional_testing > ${{ github.workspace }}/Data/DebugDatabaseDumps/mysqldump.sql
fi
exit $retVal
- name: Upload Postgres/Mysql DB dump
uses: actions/upload-artifact@v4
if: always()
with:
name: database-dumps-${{ matrix.php }}-${{ matrix.db.engine }}
path: Data/DebugDatabaseDumps
- name: Fail pipeline if ES CR tests broke (after uploading artifacts)
if: steps.escrtests.outcome != 'success'
# Surface failure here so the upload-artifact step has a chance to run.
# See https://stackoverflow.com/a/58003436/4921449
run: |
echo '${{ toJSON(steps) }}'
exit 1
buildall:
if: "!contains(github.event.head_commit.message, '[skip ci]')"
runs-on: ubuntu-latest
name: CI build (matrix)
needs: [static-analysis, unit-tests, functional-tests, parallel-tests, behavioral-tests]
steps:
- name: Check matrix status
if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}
run: exit 1