Skip to content

Testing and CI

Mikhail Deynekin edited this page Oct 11, 2026 · 3 revisions

Testing & CI

The suite is the contract: 329 tests / 966 assertions run against every combination of PHP version and artifact, and several tests are the architecture documentation (no-eval/no-globals scan, engine independence, security containment).

Running tests

php8.3 tests/run.php                          # source tree (default)
php8.5 tests/run.php                          # source tree, newest PHP
php8.3 tests/run.php --lib=php83/html.php     # against the 8.3 bundle
php8.5 tests/run.php --lib=php85/html.php     # against the 8.5 bundle
php8.5 tests/run.php --filter=ssi --verbose   # filtered
php8.3 tests/run.php --list                   # inventory
bash tools/run-matrix.sh                      # the whole matrix + rebuild

Exit codes: 0 green Β· 1 failures Β· 2 harness error. Two tests skip in bundle mode (the per-file architecture scans) β€” everything else is artifact-independent.

The matrix

src php83 bundle php85 bundle
PHP 8.3 βœ… 329 βœ… 327 β€” (parse error by design)
PHP 8.4 βœ… βœ… β€”
PHP 8.5 βœ… 329 βœ… 327 βœ… 327

What's covered (14 case files)

01 config/container/scope/request Β· 02 support/io/pathguard Β· 03 escaper/cleaner Β· 04 cache/log/events Β· 05 SSI core Β· 06 SSI conditionals/exec Β· 07 substitution/filters Β· 08 structure tags Β· 09 i18n (incl. binary MO built in-test) Β· 10 render API/cache/stats Β· 11 linter Β· 12 WordPress (bridge/adapters/toolkit/CLI/admin/plugin files against a 60-function WP stub set) Β· 13 security (containment, budgets, token-level no-eval/no-globals scan, strict-types scan) Β· 14 CLI (subprocess exit codes).

Determinism: UTC pinned, FrozenClock injection, fixture tree under tests/tmp/ (auto-cleaned), per-test $GLOBALS/superglobal snapshot-restore, no network.

Writing tests

<?php
// tests/cases/15_my_feature.test.php
declare(strict_types=1);

group('My feature');

test('does the thing', function (): void {
    $engine = test_engine(['syntax' => ['auto_escape' => false]]);  // bootstrap helper
    assertSame('x', $engine->renderString('%{v}', ['v' => 'x']));
});

Runner API: group() test() assertSame/True/False/Null assertContains/ NotContains assertMatches assertThrows assertInstanceOf skip/skipIf fail. Helpers from tests/bootstrap.php: test_engine(array $config, ?FrozenClock), test_request(), test_options(), fixture_write/path/dir, run_isolated_php(), ssi() (in 05).

Rules of the house:

  1. New behaviour β‡’ new assertions in the matching case file (or a new one).
  2. Security-relevant change β‡’ a 13_security case proving the guard.
  3. Bundle-visible change β‡’ run tools/build.php + both --lib legs.
  4. WordPress-touching change β‡’ must pass against tests/wp-stubs.php (extend the stubs rather than skipping).

Continuous integration

.github/workflows/ci.yml:

flowchart LR
    PR[push / PR] --> T["tests job Γ—9\nPHP 8.3Β·8.4Β·8.5 Γ— srcΒ·php83Β·php85\n(8.5 bundle excluded below 8.5)"]
    PR --> B[build bundles] --> C["build.php --check\n(drift gate)"] --> T
    PR --> S["PHPStan level 6\n(+baseline)"]
    PR --> F["PHP-CS-Fixer\n(dry run)"]
    PR --> W["wiki links\n(tools/check-wiki-links.php)"]
Loading
  • Drift gate: bundles are rebuilt in CI and compared byte-for-byte with the committed ones β€” a PR that edits src/ without rebuilding fails.
  • PHPStan: level 6, [OK] No errors with an 8-entry baseline (phpstan-baseline.neon, all literal-type-inference cosmetics). Baseline entries may shrink, never grow, without review.
  • CS: PER-CS2.0 + strict types + alpha imports via .php-cs-fixer.dist.php (dry-run in CI; run it locally before pushing).

Static analysis & style locally

# PHPStan (phar, no install)
curl -sSLo phpstan.phar https://github.com/phpstan/phpstan/releases/latest/download/phpstan.phar
php8.3 phpstan.phar analyse -c phpstan.neon --memory-limit=1G

# PHP-CS-Fixer
PHP_CS_FIXER_IGNORE_ENV=1 php-cs-fixer fix --dry-run --diff

Benchmarks

php8.3 tools/bench.php 2000
# render (loops+ssi+i18n)   ~0.72 ms/op  Β· ~1.4k ops/s
# renderString (small)      ~0.014 ms/op Β· ~69k ops/s
# processSsi (5 directives) ~0.018 ms/op Β· ~54k ops/s
# t() with catalogue        ~0.002 ms/op Β· ~626k ops/s

Reference box: 2-vCPU container, PHP 8.3, opcache CLI off. A CI perf gate (Β±10 % regression fail) is on the ROADMAP.

Releasing

  1. php tools/build.php && php tools/build.php --check
  2. bash tools/run-matrix.sh (install 8.4 locally or let CI cover it)
  3. Bump src/Version.php (+ src-8.5/Version.php), CHANGELOG.md, README badges, plugin header/readme.txt.
  4. Tag v4.x.y; attach both bundles to the GitHub release (shared-hosting users download, not clone).
  5. Publish wiki sync: bash tools/publish-wiki.sh --push.

Clone this wiki locally