Files
denkfabrik-li 7045da7450 Leave the test workflow one concurrency block, so it parses again
c05927c1 added a `concurrency:` block on the premise that the suite never
got one. It already had one, four lines above -- the hunk header of that
diff reads `@@ -50,6 +50,23 @@ concurrency:`, which is the existing block
it was appended below.

A YAML mapping cannot carry the same key twice, so the file has not
loaded since. GitHub still creates a run and then schedules nothing:

  553f5fd2  (last green)  run 33036453748  jobs=1  ci -> success
  d58e4830  (main)        run 33114046849  jobs=0  failure

Every run since has that shape, and the run list names it in passing:
those runs appear as `.github/workflows/tests.yml` where the green ones
appear as `tests`, because the `name:` key sits inside the file that did
not parse. `linter` is unaffected -- it carries one block -- which is why
351da21e shows a green linter beside a failed tests run, and the tree
reads as half-checked rather than unchecked.

Reproduced with a parser rather than inferred from the job count:

  before -> THREW: Duplicate key "concurrency" detected at line 66.
  after  -> parsed ok, top-level keys: name,on,concurrency,jobs

Kept the second block, verbatim, because it is the one c05927c1 meant to
end up with and its comment carries the reasoning -- including the
tradeoff that an intermediate commit on `main` can end up with no run of
its own. The two group keys are interchangeable: `github.workflow` is
constant within a workflow, so `tests-${{ github.workflow }}-${{ github.ref }}`
and `tests-${{ github.ref }}` produce the same grouping. Worth knowing
that lint.yml still uses the first shape, if you would rather the two
files read alike.

No test. The failure is loud on the next push, and a test that parses a
workflow file would be a second place to keep the same rule.
2026-08-27 23:53:37 +02:00

155 lines
5.6 KiB
YAML

name: tests
on:
push:
branches:
- develop
- main
paths-ignore:
# Files no code reads and no test covers. Deliberately NOT listed:
# CHANGELOG.md, which ReleaseNotes parses and ReleaseNotesTest
# covers, and docs/, whose only two tracked files are served by
# ApiDocsController and OpenApiController. A malformed edit to
# either is exactly the thing that must not skip the suite.
#
# Repeated verbatim under pull_request: GitHub Actions does not
# support YAML anchors.
- 'README.md'
- 'CONTRIBUTING.md'
- 'SECURITY.md'
- 'LICENSING.md'
- 'CLA-ENTITY.md'
- 'CLA-INDIVIDUAL.md'
- 'INSTALL.md'
- 'UPDATE.md'
- 'DOCKER.md'
- 'MIGRATING-FROM-V1.md'
- 'docker/production/dockerhub-overview.md'
- '.github/screenshots/**'
pull_request:
branches:
- develop
- main
paths-ignore:
- 'README.md'
- 'CONTRIBUTING.md'
- 'SECURITY.md'
- 'LICENSING.md'
- 'CLA-ENTITY.md'
- 'CLA-INDIVIDUAL.md'
- 'INSTALL.md'
- 'UPDATE.md'
- 'DOCKER.md'
- 'MIGRATING-FROM-V1.md'
- 'docker/production/dockerhub-overview.md'
- '.github/screenshots/**'
# A second push supersedes the first — the later run covers a superset of
# what the earlier one was checking, so finishing both buys nothing and
# costs a runner.
#
# Keyed on the ref, so `main` and a branch never cancel each other. A push
# to a branch that also has a pull request open produces two events with
# two different refs, which is why they do not fight either.
#
# The tradeoff worth naming: on `main` this means an intermediate commit
# can end up with no run of its own when two pushes land together. That is
# accepted here — what is being verified is the state of the branch, and
# the run that survives is the one that includes both commits. If a commit
# ever needs its own green tick (a bisect, a release audit), push it alone.
concurrency:
group: tests-${{ github.ref }}
cancel-in-progress: true
jobs:
ci:
runs-on: ubuntu-latest
# This job has no MySQL/db service — it's SQLite-only (see
# phpunit.xml) — but .env.example's DB_CONNECTION=mysql/DB_HOST=db is
# for the local docker-compose stack. Bare artisan calls that run
# outside phpunit.xml's env (composer's package:discover,
# key:generate) would otherwise try to reach a "db" host that
# doesn't exist here. Same story for CACHE_STORE: every process
# boot reads a mail-config cache entry (PlatformServiceProvider::
# boot() -> MailConfigApplier::apply()), which needs a store that
# works without a migrated schema this early.
env:
CACHE_STORE: array
DB_CONNECTION: sqlite
# config/database.php's sqlite connection reuses DB_DATABASE for
# the file path — .env.example's DB_DATABASE=projectsend (the
# MySQL database name) would otherwise make sqlite look for a
# file literally named "projectsend".
DB_DATABASE: database/database.sqlite
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.4
tools: composer:v2
coverage: xdebug
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
# community-modules is the community edition's companion package,
# resolved from its public GitHub repository (the vcs entry in
# composer.json). A plain clone installs it with no local packages/
# checkout — cloud-modules is not required at all. COMPOSER_AUTH just
# hands composer the runner's token so the GitHub API calls it makes
# to fetch the package are not subject to the anonymous rate limit.
# composer install's post-autoload-dump script boots the app
# (package:discover), which needs the sqlite file to already exist
# even before .env is copied — config/database.php defaults to it.
- name: Create SQLite Database
run: touch database/database.sqlite
- name: Install PHP Dependencies
env:
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GITHUB_TOKEN }}"}}'
run: composer install --no-interaction --prefer-dist --optimize-autoloader
- name: Install Node Dependencies
run: npm ci
# tsconfig.json maps the "ziggy-js" import to vendor/tightenco/ziggy,
# so this needs PHP deps installed first.
- name: Typecheck Frontend
run: npm run types
- name: Build Assets
run: npm run build
- name: Copy Environment File
run: cp .env.example .env
- name: Generate Application Key
run: php artisan key:generate
- name: Static Analysis
run: ./vendor/bin/phpstan analyse --no-progress
# `--parallel` rather than a shorter suite. One process took 191s of
# this job's 4m30s; the same 1763 tests across the runner's cores
# take about a third of that, with nothing skipped. paratest is
# already a dev dependency (via Pest), so this needs no new install.
#
# `:memory:` explicitly: parallel testing gives each process its own
# database, and an in-memory one per process is what the suite is
# verified against locally. The job-level DB_DATABASE above is a file
# path, which parallel workers would have to create and migrate
# individually — a difference in behaviour with nothing to gain.
- name: Tests
run: ./vendor/bin/pest --parallel
env:
DB_DATABASE: ':memory:'