Skip to content

Commercial launch core: repository hardening and release programme (#… #298

Commercial launch core: repository hardening and release programme (#…

Commercial launch core: repository hardening and release programme (#… #298

Workflow file for this run

name: Test All
on:
push:
branches:
- main
pull_request:
branches:
- main
types: [opened, synchronize, reopened, ready_for_review]
paths:
- 'packages/**/*.php'
- 'packages/**/composer.json'
- 'tests/**/*.php'
- 'composer.json'
- 'composer.lock'
- 'docs/packages/catalog.md'
- 'scripts/**/*.php'
- 'docs/getting-started/install.md'
- 'docs/getting-started/quickstart.md'
- '.docker/Dockerfile'
- '.github/workflows/test-fast-pr.yml'
- '.github/workflows/test-full.yml'
- 'phpunit.xml'
- 'phpunit-coverage.xml'
workflow_dispatch:
inputs:
cell:
description: 'Optional exact behaviour or unit cell (for example: l12-feature-admin). Leave blank for the complete workflow.'
required: false
type: string
concurrency:
group: >-
${{ github.event_name == 'workflow_dispatch' && inputs.cell != ''
&& format('{0}-phpunit-target-{1}', github.ref, inputs.cell)
|| format('{0}-phpunit-full', github.head_ref || github.ref) }}
cancel-in-progress: true
permissions:
contents: read
env:
CACHE_STORE: array
QUEUE_CONNECTION: sync
SESSION_DRIVER: array
jobs:
fast-pr:
if: github.event_name == 'pull_request' && github.event.pull_request.draft == false
name: Fast PR gate
uses: ./.github/workflows/test-fast-pr.yml
matrix:
name: Resolve repository-owned Test All matrix
runs-on: ubuntu-latest
outputs:
behaviour: ${{ steps.matrix.outputs.behaviour }}
unit: ${{ steps.matrix.outputs.unit }}
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
with:
php-version: 8.4
coverage: none
- name: Export shared matrix
id: matrix
shell: bash
env:
TARGET_CELL: ${{ inputs.cell }}
run: |
cell_argument=()
if [ -n "$TARGET_CELL" ]; then
php scripts/test-all-matrix.php target --cell="$TARGET_CELL" > /dev/null
cell_argument=(--cell="$TARGET_CELL")
fi
echo "behaviour=$(php scripts/test-all-matrix.php behaviour "${cell_argument[@]}")" >> "$GITHUB_OUTPUT"
echo "unit=$(php scripts/test-all-matrix.php unit "${cell_argument[@]}")" >> "$GITHUB_OUTPUT"
sentinel:
needs: [fast-pr, matrix]
if: >-
always()
&& needs.matrix.result == 'success'
&& (needs.fast-pr.result == 'success' || needs.fast-pr.result == 'skipped')
&& (github.event_name != 'pull_request' || github.event.pull_request.draft == false)
&& !(github.event_name == 'workflow_dispatch' && inputs.cell != '')
name: Sentinel - portability, cache, migrations, destructive schema
runs-on: ubuntu-latest
timeout-minutes: 15
env:
COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.ACCESS_TOKEN }}"} }'
PAO_DISABLE: 1
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: test_cms_sentinel
ports:
- 3306/tcp
options: >-
--health-cmd "mysqladmin ping -h 127.0.0.1 -proot || exit 1"
--health-interval 10s
--health-timeout 5s
--health-retries 10
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
with:
php-version: 8.4
extensions: curl, dom, fileinfo, gd, intl, json, libxml, mbstring, pdo_mysql, pdo_sqlite, simplexml, sqlite3, zip
tools: composer:v2
coverage: none
- name: Setup problem matchers
run: |
echo "::add-matcher::${{ runner.tool_cache }}/php.json"
echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- name: Prepare exact Laravel 13 dependencies
run: php scripts/prepare-test-all-dependencies.php --laravel='13.*' --testbench='11.*'
- name: Run both sentinel cells
shell: bash
env:
DB_HOST: 127.0.0.1
DB_PORT: ${{ job.services.mysql.ports['3306'] }}
DB_DATABASE: test_cms_sentinel
DB_USERNAME: root
DB_PASSWORD: root
run: |
set +e
php scripts/run-test-all-cell.php --cell=sentinel-unit --output-dir=test-all-results
unit_exit=$?
php scripts/run-test-all-cell.php --cell=sentinel-database --output-dir=test-all-results
database_exit=$?
if [ "$unit_exit" -ne 0 ] || [ "$database_exit" -ne 0 ]; then
exit 1
fi
- name: Upload sentinel evidence
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: test-all-sentinel
path: test-all-results
if-no-files-found: warn
test-behaviour:
needs: [sentinel, matrix]
if: >-
always()
&& needs.matrix.result == 'success'
&& (needs.sentinel.result == 'success'
|| (github.event_name == 'workflow_dispatch' && inputs.cell != ''))
&& (inputs.cell == '' || !contains(inputs.cell, '-unit-'))
name: Test All - P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.test_suite }} - ${{ matrix.package }} - DBmysql
runs-on: ubuntu-latest
env:
COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.ACCESS_TOKEN }}"} }'
PAO_DISABLE: 1
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.matrix.outputs.behaviour) }}
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: test_cms_multi
ports:
- 3306/tcp
options: >-
--health-cmd "mysqladmin ping -h 127.0.0.1 -proot || exit 1"
--health-interval 10s
--health-timeout 5s
--health-retries 10
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
with:
php-version: ${{ matrix.php }}
extensions: curl, dom, fileinfo, gd, intl, json, libxml, mbstring, pdo_mysql, pdo_sqlite, simplexml, sqlite3, zip
tools: composer:v2
coverage: none
- name: Setup problem matchers
run: |
echo "::add-matcher::${{ runner.tool_cache }}/php.json"
echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- name: Prepare exact framework dependencies
run: php scripts/prepare-test-all-dependencies.php --laravel='${{ matrix.laravel }}' --testbench='${{ matrix.testbench }}'
- name: Tune MySQL for the write-heavy test suite
run: |
mysql \
--host=127.0.0.1 \
--port="${{ job.services.mysql.ports['3306'] }}" \
--user=root \
--password=root \
--execute="
SET GLOBAL innodb_redo_log_capacity = 2147483648;
SET GLOBAL innodb_flush_log_at_trx_commit = 2;
SET GLOBAL sync_binlog = 0;
"
- name: Run ${{ matrix.test_suite }} tests
env:
DB_HOST: 127.0.0.1
DB_PORT: ${{ job.services.mysql.ports['3306'] }}
DB_DATABASE: test_cms_multi
DB_USERNAME: root
DB_PASSWORD: root
run: php scripts/run-test-all-cell.php --cell='${{ matrix.id }}' --output-dir=test-all-results
- name: Upload Test All evidence
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: junit-${{ matrix.artifact_slug }}
path: test-all-results
if-no-files-found: warn
- name: Extract engineering metrics
id: engineering-metrics
if: success() && matrix.laravel == '13.*'
run: |
php scripts/extract-pest-metrics.php "test-all-results/${{ matrix.log }}" --github-output
php scripts/extract-phpstan-level.php --github-output
- name: Write engineering metrics artifact
if: success() && matrix.laravel == '13.*'
env:
TESTS: ${{ steps.engineering-metrics.outputs.tests }}
ASSERTIONS: ${{ steps.engineering-metrics.outputs.assertions }}
PHPSTAN_LEVEL: ${{ steps.engineering-metrics.outputs.phpstan_level }}
run: |
php -r 'file_put_contents(
"engineering-metrics-${{ matrix.test_suite_slug }}-${{ matrix.package }}.json",
json_encode([
"tests" => (int) getenv("TESTS"),
"assertions" => (int) getenv("ASSERTIONS"),
"phpstan_level" => (int) getenv("PHPSTAN_LEVEL"),
], JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT) . PHP_EOL,
);'
- name: Upload engineering metrics artifact
if: success() && matrix.laravel == '13.*'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: engineering-metrics-${{ matrix.artifact_slug }}
path: engineering-metrics-${{ matrix.test_suite_slug }}-${{ matrix.package }}.json
if-no-files-found: error
test-unit:
needs: [sentinel, matrix]
if: >-
always()
&& needs.matrix.result == 'success'
&& (needs.sentinel.result == 'success'
|| (github.event_name == 'workflow_dispatch' && inputs.cell != ''))
&& (inputs.cell == '' || contains(inputs.cell, '-unit-'))
name: Test All - P${{ matrix.php }} - L${{ matrix.laravel }} - Unit - ${{ matrix.package }}
runs-on: ubuntu-latest
env:
COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.ACCESS_TOKEN }}"} }'
PAO_DISABLE: 1
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.matrix.outputs.unit) }}
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
with:
php-version: ${{ matrix.php }}
extensions: curl, dom, fileinfo, gd, intl, json, libxml, mbstring, pdo_sqlite, simplexml, sqlite3, zip
tools: composer:v2
coverage: none
- name: Setup problem matchers
run: |
echo "::add-matcher::${{ runner.tool_cache }}/php.json"
echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- name: Prepare exact framework dependencies
run: php scripts/prepare-test-all-dependencies.php --laravel='${{ matrix.laravel }}' --testbench='${{ matrix.testbench }}'
- name: Run ${{ matrix.package }} Unit tests
run: php scripts/run-test-all-cell.php --cell='${{ matrix.id }}' --output-dir=test-all-results
- name: Upload Test All evidence
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: junit-${{ matrix.artifact_slug }}
path: test-all-results
if-no-files-found: warn
- name: Extract engineering metrics
id: engineering-metrics
if: success() && matrix.laravel == '13.*'
run: |
php scripts/extract-pest-metrics.php "test-all-results/${{ matrix.log }}" --github-output
php scripts/extract-phpstan-level.php --github-output
- name: Write engineering metrics artifact
if: success() && matrix.laravel == '13.*'
env:
TESTS: ${{ steps.engineering-metrics.outputs.tests }}
ASSERTIONS: ${{ steps.engineering-metrics.outputs.assertions }}
PHPSTAN_LEVEL: ${{ steps.engineering-metrics.outputs.phpstan_level }}
run: |
php -r 'file_put_contents(
"engineering-metrics-unit-${{ matrix.package_slug }}.json",
json_encode([
"tests" => (int) getenv("TESTS"),
"assertions" => (int) getenv("ASSERTIONS"),
"phpstan_level" => (int) getenv("PHPSTAN_LEVEL"),
], JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT) . PHP_EOL,
);'
- name: Upload engineering metrics artifact
if: success() && matrix.laravel == '13.*'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: engineering-metrics-${{ matrix.artifact_slug }}
path: engineering-metrics-unit-${{ matrix.package_slug }}.json
if-no-files-found: error
engineering-metrics:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
name: Aggregate engineering metrics
needs: [test-behaviour, test-unit]
runs-on: ubuntu-latest
steps:
- name: Download engineering metrics artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: engineering-metrics-l13-*
path: engineering-metrics
merge-multiple: true
- name: Aggregate engineering metrics
id: engineering-metrics
run: |
php <<'PHP'
<?php
$files = glob('engineering-metrics/*.json');
if ($files === false || count($files) !== 11) {
throw new RuntimeException('Expected engineering metrics from five Feature packages, Integration, and five Unit packages.');
}
$tests = 0;
$assertions = 0;
$phpstanLevels = [];
foreach ($files as $file) {
$metrics = json_decode((string) file_get_contents($file), true, flags: JSON_THROW_ON_ERROR);
$tests += (int) ($metrics['tests'] ?? 0);
$assertions += (int) ($metrics['assertions'] ?? 0);
$phpstanLevels[] = (int) ($metrics['phpstan_level'] ?? 0);
}
$phpstanLevels = array_values(array_unique($phpstanLevels));
if (count($phpstanLevels) !== 1) {
throw new RuntimeException('Laravel 13 test jobs reported inconsistent PHPStan levels.');
}
file_put_contents(
getenv('GITHUB_OUTPUT'),
"tests={$tests}\nassertions={$assertions}\nphpstan_level={$phpstanLevels[0]}\n",
FILE_APPEND,
);
fwrite(STDOUT, "Aggregated engineering metrics: tests={$tests}, assertions={$assertions}, phpstan_level={$phpstanLevels[0]}\n");
PHP
- name: Send repository_dispatch to capell-app
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
env:
ENGINEERING_METRICS_DISPATCH_TOKEN: ${{ secrets.ACCESS_TOKEN }}
with:
github-token: ${{ secrets.ACCESS_TOKEN }}
script: |
if (!process.env.ENGINEERING_METRICS_DISPATCH_TOKEN) {
core.setFailed('Missing ACCESS_TOKEN secret.');
return;
}
const client_payload = {
source: 'capell',
tests: Number(`${{ steps.engineering-metrics.outputs.tests }}`),
assertions: Number(`${{ steps.engineering-metrics.outputs.assertions }}`),
run_url: `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}`,
sha: context.sha,
phpstan_level: Number(`${{ steps.engineering-metrics.outputs.phpstan_level }}`),
};
await github.rest.repos.createDispatchEvent({
owner: 'capell-app',
repo: 'capell-app',
event_type: 'engineering-metrics',
client_payload,
});
core.info(`Dispatched engineering-metrics: ${JSON.stringify(client_payload)}`);