build(deps): bump actions/upload-artifact from 4.6.2 to 7.0.1 #183
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Tests | |
| on: | |
| push: | |
| branches: [master] | |
| pull_request: | |
| branches: [master] | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: tests-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| validate: | |
| name: Validate composer.json | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # master | |
| with: | |
| php-version: '8.4' | |
| tools: composer | |
| coverage: none | |
| - run: composer validate --strict | |
| stub-drift: | |
| name: Stub / arginfo drift | |
| runs-on: ubuntu-latest | |
| needs: validate | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # master | |
| with: | |
| php-version: '8.4' | |
| # gen_stub.php parses the stub via the tokenizer extension and | |
| # pulls PHP-Parser at runtime; both are in setup-php's default set. | |
| coverage: none | |
| - name: Fetch gen_stub.php from php-src | |
| # gen_stub.php is the canonical *_arginfo.h generator (same script | |
| # the /php-stub-regen skill runs locally). It lives in php-src's | |
| # build/ dir and self-installs PHP-Parser on first run, so a | |
| # shallow sparse checkout of just build/ is all we need — no full | |
| # php-src tree, no php-src build. Pinned to tag php-8.4.9: the | |
| # generated "Stub hash" header format is stable across the 8.x | |
| # line (verified: 8.4.x gen_stub reproduces the committed header | |
| # byte-for-byte). Bump this pin only if a deliberate regen changes | |
| # the header format. | |
| run: | | |
| git clone --depth=1 --branch=php-8.4.9 --filter=blob:none --sparse \ | |
| https://github.com/php/php-src.git /tmp/php-src | |
| git -C /tmp/php-src sparse-checkout set build | |
| test -f /tmp/php-src/build/gen_stub.php | |
| - name: Regenerate arginfo and check for drift | |
| # gen_stub emits fastchart_arginfo.h next to the stub (repo root). | |
| # If it differs from the committed header, the stub was edited | |
| # without regenerating — fail and point at the fix. | |
| run: | | |
| php /tmp/php-src/build/gen_stub.php fastchart.stub.php | |
| if ! git diff --exit-code fastchart_arginfo.h; then | |
| echo "::error::fastchart_arginfo.h is out of sync with fastchart.stub.php — run /php-stub-regen and commit the regenerated header" | |
| exit 1 | |
| fi | |
| echo "arginfo is in sync with the stub" | |
| pie-smoke: | |
| name: PIE install smoke | |
| runs-on: ubuntu-latest | |
| needs: validate | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # master | |
| with: | |
| php-version: '8.4' | |
| extensions: gd | |
| tools: phpize, php-config | |
| coverage: none | |
| - name: Install PIE build prerequisites | |
| run: | | |
| sudo apt-get update -qq | |
| sudo apt-get install -y --no-install-recommends bison libtool-bin \ | |
| libfreetype-dev libpng-dev libjpeg-turbo8-dev libwebp-dev pkg-config | |
| - name: Download PIE | |
| run: | | |
| curl -fsSL -o /tmp/pie.phar https://github.com/php/pie/releases/download/1.4.8/pie.phar | |
| echo 'ef9f19c2698334aa8ce8fc458c8cf2a31a2fd6a29230216dbde3422343cf952d /tmp/pie.phar' | sha256sum -c - | |
| chmod +x /tmp/pie.phar | |
| - name: PIE build smoke | |
| run: | | |
| php /tmp/pie.phar repository:add path . | |
| php /tmp/pie.phar build iliaal/fastchart:*@dev --enable-fastchart | |
| missing-codecs: | |
| name: PHP 8.4 - optional codecs unavailable | |
| runs-on: ubuntu-latest | |
| needs: validate | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # master | |
| with: | |
| php-version: '8.4' | |
| tools: phpize, php-config | |
| coverage: none | |
| - name: Install mandatory build dependency | |
| run: | | |
| sudo apt-get update -qq | |
| sudo apt-get install -y --no-install-recommends \ | |
| libfreetype-dev pkg-config | |
| - name: Build without optional codecs | |
| run: | | |
| set -o pipefail | |
| chmod +x scripts/ci-pkg-config-without-codecs | |
| mkdir -p "$RUNNER_TEMP/fastchart-no-codecs-bin" | |
| ln -s "$PWD/scripts/ci-pkg-config-without-codecs" \ | |
| "$RUNNER_TEMP/fastchart-no-codecs-bin/pkg-config" | |
| export PATH="$RUNNER_TEMP/fastchart-no-codecs-bin:$PATH" | |
| phpize | |
| ./configure --enable-fastchart --enable-fastchart-dev \ | |
| 2>&1 | tee "$RUNNER_TEMP/configure-no-codecs.log" | |
| for library in libpng libjpeg libwebp; do | |
| if ! grep -Fq "$library not found" \ | |
| "$RUNNER_TEMP/configure-no-codecs.log"; then | |
| echo "::error::$library was not disabled by the pkg-config shim" | |
| exit 1 | |
| fi | |
| done | |
| if grep -Eq '^#define HAVE_LIB(PNG|JPEG|WEBP) 1' config.h; then | |
| echo "::error::an optional codec define is present in config.h" | |
| grep -E '^#define HAVE_LIB(PNG|JPEG|WEBP) 1' config.h | |
| exit 1 | |
| fi | |
| make -j"$(nproc)" | |
| - name: Verify SVG-only runtime and exact codec errors | |
| run: | | |
| SO="$PWD/modules/fastchart.so" | |
| test -f "$SO" | |
| if readelf -d "$SO" | grep NEEDED | \ | |
| grep -qiE 'libpng|libjpeg|libwebp'; then | |
| echo "::error::missing-codec build directly links an optional codec" | |
| exit 1 | |
| fi | |
| php -n -d extension="$SO" scripts/ci-missing-codecs.php | |
| musl-smoke: | |
| name: musl load smoke (Alpine + clang) | |
| runs-on: ubuntu-latest | |
| needs: validate | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| # Build the shared extension under musl + clang (the static-php-cli | |
| # consumer's toolchain family) and confirm php can dlopen it. The | |
| # glibc matrix can't see load failures rooted in libgcc/compiler-rt | |
| # symbols that don't resolve off-glibc. checkout runs on the glibc | |
| # host; the build runs inside the Alpine container via docker run so | |
| # the musl image needs no node (Alpine + actions/checkout don't mix). | |
| - name: Build + dlopen smoke in Alpine | |
| run: | | |
| # --enable-fastchart-dev holds the same -Werror -Wextra | |
| # -Wstrict-prototypes bar as the glibc lanes (verified clean on | |
| # clang + musl). No linux-headers package needed: vendored | |
| # plutovg resolves PATH_MAX from <limits.h>, not the kernel's | |
| # <linux/limits.h>. | |
| docker run --rm -v "$PWD":/src -w /src php:8.3-alpine sh -ec ' | |
| apk add --no-cache build-base clang autoconf pkgconf binutils \ | |
| freetype-dev libpng-dev libjpeg-turbo-dev libwebp-dev | |
| export CC=clang | |
| phpize | |
| ./configure --enable-fastchart --enable-fastchart-dev | |
| make -j4 | |
| php -d extension="$(pwd)/modules/fastchart.so" --ri fastchart | |
| if nm modules/fastchart.so | grep -E "__cpu_model|__cpu_indicator_init"; then | |
| echo "::error::fastchart.so references libgcc cpu-feature symbols; breaks static/musl/zig-cc dlopen (iliaal/fastchart#6)" | |
| exit 1 | |
| fi | |
| echo "musl dlopen + symbol audit clean" | |
| ' | |
| linux: | |
| name: PHP ${{ matrix.php }} (${{ matrix.cc }}) - Linux | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| php: ['8.1', '8.2', '8.3', '8.4', '8.5'] | |
| cc: ['gcc'] | |
| # One clang lane: clang's diagnostics differ from gcc's and catch | |
| # warnings/UB the gcc matrix misses. It does NOT catch the | |
| # static/musl dlopen class on its own (a clang glibc build bundles | |
| # the same symbols a gcc build does) — the symbol-audit step below | |
| # and the musl-smoke job cover that. | |
| include: | |
| - php: '8.4' | |
| cc: 'clang' | |
| env: | |
| MATRIX_CC: ${{ matrix.cc }} | |
| PHP_VERSION: ${{ matrix.php }} | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - name: Install build dependencies | |
| run: | | |
| sudo apt-get update -qq | |
| sudo apt-get install -y --no-install-recommends \ | |
| clang libfreetype-dev libpng-dev libjpeg-turbo8-dev libwebp-dev pkg-config | |
| - name: Setup PHP | |
| uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # master | |
| with: | |
| php-version: ${{ matrix.php }} | |
| extensions: gd | |
| tools: phpize, php-config | |
| - name: Build extension | |
| run: | | |
| export CC="${MATRIX_CC}" | |
| phpize | |
| ./configure --enable-fastchart --enable-fastchart-dev | |
| # make must fail the step itself — piping straight into grep | |
| # took grep's exit status, so a failed compile left no .so, | |
| # every test SKIPped, and only the skip ceiling caught it. | |
| set -o pipefail | |
| make -j$(nproc) 2>&1 | tee /tmp/build.log | |
| set +o pipefail | |
| WARNINGS=$(grep -c 'warning:' /tmp/build.log || true) | |
| if [ "$WARNINGS" -gt 0 ]; then | |
| echo "::error::Build produced $WARNINGS warning(s) with ${MATRIX_CC}" | |
| exit 1 | |
| fi | |
| - name: Guard against non-static-linkable runtime symbols | |
| run: | | |
| # __builtin_cpu_supports / __builtin_cpu_init reference libgcc's | |
| # __cpu_model and __cpu_indicator_init. On glibc the compiler | |
| # bundles them into the .so so it loads here, but they go | |
| # unresolved under fully-static / musl / zig-cc links, so the | |
| # object fails to dlopen downstream (static-php-cli) with an | |
| # undefined-symbol error and `php --ri` exits non-zero. Their | |
| # presence in the object at all is the tell, regardless of | |
| # compiler. Detect CPU features with __get_cpuid from <cpuid.h> | |
| # instead. Reported in iliaal/fastchart#6. | |
| if nm modules/fastchart.so | grep -E '__cpu_model|__cpu_indicator_init'; then | |
| echo "::error::fastchart.so references libgcc cpu-feature symbols (__builtin_cpu_supports?); breaks static/musl/zig-cc dlopen — use __get_cpuid" | |
| exit 1 | |
| fi | |
| echo "symbol audit: no libgcc cpu-feature symbols" | |
| - name: Verify gd capabilities (guard against silent mass-skips) | |
| # ~91 phpts declare `--EXTENSIONS-- gd` and round-trip raster | |
| # output through imagecreatefromstring / imagecreatefromwebp for | |
| # pixel inspection. If gd vanishes, or its build loses WebP / | |
| # JPEG / FreeType support, those tests SKIP silently and a "green" | |
| # run hides a real regression. Assert the capabilities up front so | |
| # the loss fails fast with a clear message instead of hiding in | |
| # the skip counter. This lane loads gd via setup-php's | |
| # `extensions: gd`. | |
| run: | | |
| php -r ' | |
| if (!extension_loaded("gd")) { | |
| fwrite(STDERR, "gd not loaded — ~91 raster round-trip tests would skip silently\n"); | |
| exit(1); | |
| } | |
| $i = gd_info(); | |
| $need = ["WebP Support", "JPEG Support", "FreeType Support", "PNG Support"]; | |
| $missing = []; | |
| foreach ($need as $k) { if (empty($i[$k])) { $missing[] = $k; } } | |
| if ($missing) { | |
| fwrite(STDERR, "gd is missing: " . implode(", ", $missing) . | |
| " — raster round-trip tests would skip silently\n"); | |
| exit(1); | |
| } | |
| echo "gd capabilities OK: " . implode(", ", $need) . "\n"; | |
| ' | |
| - name: Run tests | |
| run: | | |
| export TEST_PHP_EXECUTABLE=$(which php) | |
| # gd is already loaded by shivammathur/setup-php's extensions: gd | |
| # (writes extension=gd to php.ini) and is used by tests for PHP- | |
| # side image validation (imagecreatefromstring / imagesx). | |
| # Adding -d extension=gd on top makes PHP 8.4+ emit "Module | |
| # already loaded" warnings that prepend to every test's stdout | |
| # and break --EXPECT-- matching. | |
| export TEST_PHP_ARGS="-d extension=$(pwd)/modules/fastchart.so" | |
| export NO_INTERACTION=1 | |
| # Stream output so failure context lands in the log even | |
| # when the run aborts before the trailing summary. Tee to | |
| # a file so we can assert "Tests failed: N" cleanly. | |
| set +e | |
| php run-tests.php --show-diff -g FAIL,BORK,LEAK,XLEAK tests/ 2>&1 | tee /tmp/test-output.txt | |
| rc=${PIPESTATUS[0]} | |
| set -e | |
| if [ "$rc" -ne 0 ]; then | |
| echo "::error::run-tests.php exited $rc on PHP ${PHP_VERSION} - Linux (crash / BORK / aborted before summary)" | |
| grep -E '^FAIL\b|^FAILED TEST|^BORK' /tmp/test-output.txt || true | |
| exit 1 | |
| fi | |
| if grep -qP '^Tests failed\s*:\s*[1-9]' /tmp/test-output.txt; then | |
| echo "::error::Tests failed on PHP ${PHP_VERSION} - Linux" | |
| grep -E '^FAIL\b|^FAILED TEST' /tmp/test-output.txt || true | |
| exit 1 | |
| fi | |
| # Skip ceiling. This lane loads gd (so the ~91 gd round-trip | |
| # tests RUN) and is built without --with-pdfio. Legitimate | |
| # skips: 7 PDF-gated + up to 12 fc_pick_font()-gated (system | |
| # font absent) + 1 comma-decimal-locale + a couple of | |
| # environment-gated (open_basedir / non-regular-source). Budget | |
| # 7 + 12 + ~5 slack = 25. A run that skips MORE than this means | |
| # a capability quietly vanished and tests are hiding in the skip | |
| # counter rather than failing — treat it as a failure. Bump this | |
| # only alongside a deliberate new skip-gated test. | |
| SKIPPED=$(grep -oP '^Tests skipped\s*:\s*\K[0-9]+' /tmp/test-output.txt | head -1) | |
| SKIPPED=${SKIPPED:-0} | |
| echo "Tests skipped: $SKIPPED (ceiling 25)" | |
| if [ "$SKIPPED" -gt 25 ]; then | |
| echo "::error::Skip count $SKIPPED exceeds ceiling 25 on PHP ${PHP_VERSION} - Linux — a capability likely vanished (gd/webp/fonts); tests are hiding in the skip counter" | |
| grep -E '^SKIP\b' /tmp/test-output.txt || true | |
| exit 1 | |
| fi | |
| pdf: | |
| name: PHP ${{ matrix.php }} - PDF backend (--with-pdfio) | |
| runs-on: ubuntu-latest | |
| needs: validate | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| php: ['8.4'] | |
| env: | |
| # Keep in sync with the `pinned:` in .upstream/pdfio.yml. | |
| PDFIO_VERSION: '1.6.4' | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - name: Install build dependencies | |
| run: | | |
| sudo apt-get update -qq | |
| sudo apt-get install -y --no-install-recommends \ | |
| clang libfreetype-dev libpng-dev libjpeg-turbo8-dev libwebp-dev \ | |
| zlib1g-dev pkg-config | |
| - name: Build + install pdfio | |
| # pdfio is not packaged in apt and is not vendored — the PDF | |
| # backend links it statically from a system prefix. Build the | |
| # pinned release into /opt/pdfio and expose its pdfio.pc via | |
| # PKG_CONFIG_PATH. Static (--disable-shared) so the resulting | |
| # fastchart.so carries no libpdfio runtime dependency. | |
| run: | | |
| curl -fsSL -o /tmp/pdfio.tar.gz \ | |
| "https://github.com/michaelrsweet/pdfio/releases/download/v${PDFIO_VERSION}/pdfio-${PDFIO_VERSION}.tar.gz" | |
| mkdir -p /tmp/pdfio-src | |
| tar xzf /tmp/pdfio.tar.gz -C /tmp/pdfio-src --strip-components=1 | |
| cd /tmp/pdfio-src | |
| ./configure --prefix=/opt/pdfio --disable-shared --enable-static | |
| make -j"$(nproc)" libpdfio.a | |
| sudo make install | |
| - name: Setup PHP | |
| uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # master | |
| with: | |
| php-version: ${{ matrix.php }} | |
| extensions: gd | |
| tools: phpize, php-config | |
| - name: Build extension with PDF enabled | |
| run: | | |
| export PKG_CONFIG_PATH=/opt/pdfio/lib/pkgconfig:${PKG_CONFIG_PATH:-} | |
| phpize | |
| ./configure --enable-fastchart --enable-fastchart-dev --with-pdfio=/opt/pdfio | |
| # pipefail so a failed compile fails the step (grep's status | |
| # otherwise masks make's — see the Linux lane's build step). | |
| set -o pipefail | |
| make -j"$(nproc)" 2>&1 | tee /tmp/build.log | |
| set +o pipefail | |
| WARNINGS=$(grep -c 'warning:' /tmp/build.log || true) | |
| if [ "$WARNINGS" -gt 0 ]; then | |
| echo "::error::PDF build produced $WARNINGS warning(s)" | |
| exit 1 | |
| fi | |
| - name: Guard localized static-dep symbols (nm export gate) | |
| # The PDF lane links pdfio statically, alongside the always-static | |
| # vendored plutovg/plutosvg and FreeType. --exclude-libs must | |
| # localize every one of those archives' symbols so none leak into | |
| # fastchart.so's dynamic export table — only fastchart's own PHP | |
| # entry point (get_module) should be exported. A leaked pdfio / | |
| # plutovg / plutosvg / ttf symbol means --exclude-libs didn't | |
| # take and the .so could clash with another extension's copy. | |
| # This guards the config.m4 --exclude-libs localization; it passes | |
| # once that concurrent config.m4 fix lands (both land together). | |
| run: | | |
| SO=modules/fastchart.so | |
| if ! nm -D "$SO" | grep -qE ' T get_module$'; then | |
| echo "::error::get_module is not exported from $SO — dynamic symbol table looks wrong" | |
| nm -D "$SO" | grep -i 'get_module' || true | |
| exit 1 | |
| fi | |
| # Defined/exported symbols only: ' T ' (text), ' D ' (data), | |
| # ' B ' (bss). Localized statics show as lowercase (t/d/b) or | |
| # vanish from -D entirely, so an uppercase match here is a leak. | |
| LEAKED=$(nm -D "$SO" | grep -E ' [TDB] ' | grep -iE 'pdfio|_pdfio|ttf|plutovg|plutosvg' || true) | |
| if [ -n "$LEAKED" ]; then | |
| echo "::error::static-dep symbols leaked into the dynamic export table (--exclude-libs not applied):" | |
| echo "$LEAKED" | |
| exit 1 | |
| fi | |
| echo "nm export gate OK: get_module exported; no pdfio/ttf/plutovg/plutosvg exports" | |
| - name: Verify gd capabilities (guard against silent mass-skips) | |
| # Same guard as the Linux lane: this lane also runs the full | |
| # suite, so a vanished gd (or a gd without WebP/JPEG/FreeType) | |
| # would silently skip the ~91 raster round-trip tests here too. | |
| run: | | |
| php -r ' | |
| if (!extension_loaded("gd")) { | |
| fwrite(STDERR, "gd not loaded — ~91 raster round-trip tests would skip silently\n"); | |
| exit(1); | |
| } | |
| $i = gd_info(); | |
| $need = ["WebP Support", "JPEG Support", "FreeType Support", "PNG Support"]; | |
| $missing = []; | |
| foreach ($need as $k) { if (empty($i[$k])) { $missing[] = $k; } } | |
| if ($missing) { | |
| fwrite(STDERR, "gd is missing: " . implode(", ", $missing) . | |
| " — raster round-trip tests would skip silently\n"); | |
| exit(1); | |
| } | |
| echo "gd capabilities OK: " . implode(", ", $need) . "\n"; | |
| ' | |
| - name: Run PDF tests (must execute, not skip) | |
| # The lane exists to exercise the PDF backend. Run 200/201 in | |
| # isolation first: if pdfio failed to link they would SKIP, so | |
| # "Tests passed : 2" is the proof the backend is actually built. | |
| run: | | |
| export TEST_PHP_EXECUTABLE=$(which php) | |
| export TEST_PHP_ARGS="-d extension=$(pwd)/modules/fastchart.so" | |
| export NO_INTERACTION=1 | |
| set +e | |
| php run-tests.php --show-diff \ | |
| tests/200_pdf_render_basic.phpt tests/201_pdf_render_to_file.phpt \ | |
| 2>&1 | tee /tmp/pdf-tests.txt | |
| rc=${PIPESTATUS[0]} | |
| set -e | |
| if [ "$rc" -ne 0 ]; then | |
| echo "::error::isolated PDF test runner exited $rc" | |
| exit "$rc" | |
| fi | |
| if ! grep -qP '^Tests passed\s*:\s*2\b' /tmp/pdf-tests.txt; then | |
| echo "::error::PDF tests did not both pass (pdfio not linked? see SKIP reasons above)" | |
| exit 1 | |
| fi | |
| - name: Run full suite with PDF enabled | |
| run: | | |
| export TEST_PHP_EXECUTABLE=$(which php) | |
| export TEST_PHP_ARGS="-d extension=$(pwd)/modules/fastchart.so" | |
| export NO_INTERACTION=1 | |
| set +e | |
| php run-tests.php --show-diff -g FAIL,BORK,LEAK,XLEAK tests/ 2>&1 | tee /tmp/test-output.txt | |
| rc=${PIPESTATUS[0]} | |
| set -e | |
| if [ "$rc" -ne 0 ]; then | |
| echo "::error::run-tests.php exited $rc on the PDF lane (crash / BORK / aborted before summary)" | |
| grep -E '^FAIL\b|^FAILED TEST|^BORK' /tmp/test-output.txt || true | |
| exit 1 | |
| fi | |
| if grep -qP '^Tests failed\s*:\s*[1-9]' /tmp/test-output.txt; then | |
| echo "::error::Tests failed on the PDF lane" | |
| grep -E '^FAIL\b|^FAILED TEST' /tmp/test-output.txt || true | |
| exit 1 | |
| fi | |
| # Skip ceiling. This lane loads gd AND builds --with-pdfio, so | |
| # both the ~91 gd round-trip tests and the 7 PDF tests RUN. | |
| # Legitimate skips: up to 12 fc_pick_font()-gated (system font | |
| # absent) + 1 comma-decimal-locale + a couple environment-gated. | |
| # Budget 12 + ~6 slack = 18. More means a capability quietly | |
| # vanished (gd/webp/fonts/pdfio) and tests are hiding in the | |
| # skip counter. Bump only alongside a deliberate new skip-gated | |
| # test. | |
| SKIPPED=$(grep -oP '^Tests skipped\s*:\s*\K[0-9]+' /tmp/test-output.txt | head -1) | |
| SKIPPED=${SKIPPED:-0} | |
| echo "Tests skipped: $SKIPPED (ceiling 18)" | |
| if [ "$SKIPPED" -gt 18 ]; then | |
| echo "::error::Skip count $SKIPPED exceeds ceiling 18 on the PDF lane — a capability likely vanished (gd/webp/fonts/pdfio); tests are hiding in the skip counter" | |
| grep -E '^SKIP\b' /tmp/test-output.txt || true | |
| exit 1 | |
| fi | |
| asan: | |
| name: PHP ${{ matrix.php }} - ASAN | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| php: ['8.4'] | |
| env: | |
| # detect_leaks=1 is the default but pinning it makes intent | |
| # explicit so a future ASAN_OPTIONS edit can't silently turn | |
| # leak detection off. The suppressions file | |
| # .github/lsan-suppressions.txt is loaded automatically by | |
| # run-tests.php --asan; it covers ext/gd's MINIT-time persistent | |
| # allocations (intentional, not bugs) and nothing else. | |
| ASAN_OPTIONS: exitcode=139:verify_asan_link_order=0:detect_odr_violation=0:detect_leaks=1 | |
| UBSAN_OPTIONS: print_stacktrace=1 | |
| USE_ZEND_ALLOC: 0 | |
| USE_TRACKED_ALLOC: 1 | |
| PHP_VERSION: ${{ matrix.php }} | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - name: Install build dependencies | |
| run: | | |
| sudo apt-get update -qq | |
| sudo apt-get install -y --no-install-recommends \ | |
| llvm clang re2c bison autoconf pkg-config \ | |
| libfreetype-dev libpng-dev libjpeg-turbo8-dev libwebp-dev libgd-dev \ | |
| libxml2-dev | |
| - name: Build PHP with ASAN + ext/gd + ext/simplexml | |
| # Static libasan (default — no -shared-libasan flag) keeps | |
| # the configure-time C-program test happy on the ubuntu | |
| # runner. ASan symbols live in the php-cli binary; fastchart.so | |
| # gets instrumented and resolves callbacks against the host | |
| # process. ext/gd is built directly into the ASAN PHP for the | |
| # PHP-side image validation that the tests use; libgd-dev is | |
| # only here to satisfy that build, fastchart itself no longer | |
| # links libgd. ext/simplexml (+ libxml dep) is built in so the | |
| # SVG-structure tests can use simplexml_load_string / | |
| # libxml_use_internal_errors to validate emitted XML — | |
| # otherwise those 23 tests fatal with "undefined function" | |
| # because --disable-all dropped them. The -g flag preserves | |
| # symbols so LSan stack frames resolve against the | |
| # suppressions file. | |
| run: | | |
| git clone --depth=1 --branch="PHP-${PHP_VERSION}" https://github.com/php/php-src.git /tmp/php-src | |
| cd /tmp/php-src | |
| ./buildconf --force | |
| CC=clang CXX=clang++ \ | |
| CFLAGS="-fsanitize=address,undefined -fno-sanitize=function,vptr -DZEND_TRACK_ARENA_ALLOC -g" \ | |
| LDFLAGS="-fsanitize=address,undefined" \ | |
| ./configure \ | |
| --enable-debug \ | |
| --disable-zts \ | |
| --disable-all \ | |
| --enable-cli \ | |
| --enable-gd \ | |
| --with-external-gd \ | |
| --with-libxml \ | |
| --enable-simplexml \ | |
| --prefix=/tmp/php-asan | |
| make -j$(nproc) | |
| make install | |
| - name: Build fastchart with ASAN | |
| run: | | |
| export PATH="/tmp/php-asan/bin:$PATH" | |
| phpize | |
| CC=clang CXX=clang++ \ | |
| CFLAGS="-fsanitize=address,undefined -fno-sanitize=function,vptr -DZEND_TRACK_ARENA_ALLOC -g" \ | |
| CXXFLAGS="-fsanitize=address,undefined -fno-sanitize=function,vptr -DZEND_TRACK_ARENA_ALLOC -g" \ | |
| LDFLAGS="-fsanitize=address,undefined" \ | |
| ./configure \ | |
| --enable-fastchart --enable-fastchart-dev \ | |
| --with-php-config=/tmp/php-asan/bin/php-config | |
| make -j$(nproc) | |
| - name: Render-only leak smoke (no ext/gd) | |
| # fastchart itself must report zero LSan leaks. ext/gd is | |
| # excluded here so any reported leak is fastchart's. This is | |
| # a gate against regressions in the FT_Library / target / | |
| # encoder lifecycles. | |
| run: | | |
| set -o pipefail | |
| /tmp/php-asan/bin/php \ | |
| -d extension=$(pwd)/modules/fastchart.so \ | |
| scripts/asan-render-smoke.php 2>&1 | tee /tmp/render-asan.txt | |
| if grep -q 'LeakSanitizer: detected memory leaks' /tmp/render-asan.txt; then | |
| echo "::error::fastchart-only render reported LSan leaks" | |
| exit 1 | |
| fi | |
| - name: Verify gd capabilities (guard against silent mass-skips) | |
| # This lane builds gd directly into the ASAN PHP | |
| # (--enable-gd --with-external-gd against the runner's libgd-dev), | |
| # so the ~91 gd round-trip tests EXECUTE here, they do not skip. | |
| # Assert the external-gd build kept WebP/JPEG/FreeType so a codec | |
| # gap in the runner's libgd surfaces loudly instead of silently | |
| # skipping those tests under ASAN. | |
| run: | | |
| /tmp/php-asan/bin/php -r ' | |
| if (!extension_loaded("gd")) { | |
| fwrite(STDERR, "gd not built into the ASAN PHP — ~91 raster tests would skip\n"); | |
| exit(1); | |
| } | |
| $i = gd_info(); | |
| $need = ["WebP Support", "JPEG Support", "FreeType Support", "PNG Support"]; | |
| $missing = []; | |
| foreach ($need as $k) { if (empty($i[$k])) { $missing[] = $k; } } | |
| if ($missing) { | |
| fwrite(STDERR, "ASAN gd missing: " . implode(", ", $missing) . "\n"); | |
| exit(1); | |
| } | |
| echo "ASAN gd capabilities OK: " . implode(", ", $need) . "\n"; | |
| ' | |
| - name: Run tests under ASAN | |
| run: | | |
| export TEST_PHP_EXECUTABLE=/tmp/php-asan/bin/php | |
| # gd is built into the ASAN PHP via --enable-gd above, so | |
| # there's no shared-extension load needed and -d extension=gd | |
| # on top would just emit "Module already loaded" warnings | |
| # that prepend to every test's output. | |
| export TEST_PHP_ARGS="-d extension=$(pwd)/modules/fastchart.so" | |
| export NO_INTERACTION=1 | |
| # --asan auto-loads .github/lsan-suppressions.txt and sets | |
| # USE_TRACKED_ALLOC. Drop `|| true` so a crashing run-tests.php | |
| # also fails the job — previously a non-zero exit was masked. | |
| set +e | |
| /tmp/php-asan/bin/php run-tests.php --show-diff --asan -g FAIL,BORK,LEAK,XLEAK tests/ 2>&1 | tee /tmp/test-output.txt | |
| rc=${PIPESTATUS[0]} | |
| set -e | |
| if [ "$rc" -ne 0 ]; then | |
| echo "::error::run-tests.php exited $rc under ASAN" | |
| exit 1 | |
| fi | |
| if grep -qP '^Tests failed\s*:\s*[1-9]' /tmp/test-output.txt; then | |
| echo "::error::Tests failed under ASAN" | |
| exit 1 | |
| fi | |
| if grep -qP '^Tests leaked\s*:\s*[1-9]' /tmp/test-output.txt; then | |
| echo "::error::Tests leaked under ASAN (LSan reported memory leaks)" | |
| exit 1 | |
| fi | |
| # Skip ceiling. NOTE: gd IS present in this lane (built into the | |
| # ASAN PHP above), so the ~91 gd tests run — the skip profile | |
| # matches the Linux lane, NOT a gd-less lane. Legitimate skips: | |
| # 7 PDF-gated (no --with-pdfio) + up to 12 font-gated + locale/ | |
| # environment-gated. Budget 30 (Linux's 25 + a little extra | |
| # slack for ASAN-specific environment differences). If gd were | |
| # ever dropped from this lane the capability probe above fails | |
| # first, so reaching this check means gd ran. | |
| SKIPPED=$(grep -oP '^Tests skipped\s*:\s*\K[0-9]+' /tmp/test-output.txt | head -1) | |
| SKIPPED=${SKIPPED:-0} | |
| echo "Tests skipped: $SKIPPED (ceiling 30)" | |
| if [ "$SKIPPED" -gt 30 ]; then | |
| echo "::error::Skip count $SKIPPED exceeds ceiling 30 under ASAN — a capability likely vanished; tests are hiding in the skip counter" | |
| grep -E '^SKIP\b' /tmp/test-output.txt || true | |
| exit 1 | |
| fi | |
| macos: | |
| name: PHP ${{ matrix.php }} - macOS | |
| runs-on: macos-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| php: ['8.1', '8.2', '8.3', '8.4'] | |
| env: | |
| PHP_VERSION: ${{ matrix.php }} | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - name: Install build dependencies | |
| run: brew install freetype libpng jpeg-turbo webp pkg-config | |
| - name: Setup PHP | |
| uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # master | |
| with: | |
| php-version: ${{ matrix.php }} | |
| extensions: gd | |
| tools: phpize, php-config | |
| - name: Build extension | |
| run: | | |
| # On Apple Silicon Homebrew installs into /opt/homebrew; | |
| # PKG_CONFIG_PATH usually picks up keg-only / non-default | |
| # prefixes automatically, but jpeg-turbo is keg-only so we | |
| # add it explicitly. | |
| export PKG_CONFIG_PATH="$(brew --prefix jpeg-turbo)/lib/pkgconfig:$(brew --prefix freetype)/lib/pkgconfig:${PKG_CONFIG_PATH:-}" | |
| phpize | |
| ./configure --enable-fastchart --enable-fastchart-dev | |
| make -j$(sysctl -n hw.ncpu) | |
| - name: Verify gd capabilities (guard against silent mass-skips) | |
| # Same guard as the Linux lane: the ~91 gd round-trip tests need | |
| # gd with WebP/JPEG/FreeType. macOS gd comes from setup-php's | |
| # `extensions: gd`; if that build ever loses a codec those tests | |
| # would skip silently and the lane would still go green. | |
| run: | | |
| php -r ' | |
| if (!extension_loaded("gd")) { | |
| fwrite(STDERR, "gd not loaded — ~91 raster round-trip tests would skip silently\n"); | |
| exit(1); | |
| } | |
| $i = gd_info(); | |
| $need = ["WebP Support", "JPEG Support", "FreeType Support", "PNG Support"]; | |
| $missing = []; | |
| foreach ($need as $k) { if (empty($i[$k])) { $missing[] = $k; } } | |
| if ($missing) { | |
| fwrite(STDERR, "gd is missing: " . implode(", ", $missing) . "\n"); | |
| exit(1); | |
| } | |
| echo "gd capabilities OK: " . implode(", ", $need) . "\n"; | |
| ' | |
| - name: Run tests | |
| run: | | |
| export TEST_PHP_EXECUTABLE=$(which php) | |
| # gd is already loaded by shivammathur/setup-php's extensions: gd | |
| # (writes extension=gd to php.ini). Adding -d extension=gd on top | |
| # makes PHP 8.4+ emit "Module already loaded" warnings that | |
| # prepend to every test's stdout and break --EXPECT-- matching. | |
| export TEST_PHP_ARGS="-d extension=$(pwd)/modules/fastchart.so" | |
| export NO_INTERACTION=1 | |
| set +e | |
| php run-tests.php --show-diff -g FAIL,BORK,LEAK,XLEAK tests/ 2>&1 | tee /tmp/test-output.txt | |
| rc=${PIPESTATUS[0]} | |
| set -e | |
| if [ "$rc" -ne 0 ]; then | |
| echo "::error::run-tests.php exited $rc on PHP ${PHP_VERSION} - macOS (crash / BORK / aborted before summary)" | |
| grep -E '^FAIL\b|^FAILED TEST|^BORK' /tmp/test-output.txt || true | |
| exit 1 | |
| fi | |
| if grep -Eq '^Tests failed[[:space:]]*:[[:space:]]*[1-9]' /tmp/test-output.txt; then | |
| echo "::error::Tests failed on PHP ${PHP_VERSION} - macOS" | |
| grep -E '^FAIL\b|^FAILED TEST' /tmp/test-output.txt || true | |
| exit 1 | |
| fi | |
| # Skip ceiling. macOS loads gd and is built without pdfio. | |
| # Legitimate skips mirror Linux (7 PDF + up to 12 font + | |
| # locale), plus macOS-only environment skips: 14 atomic-output | |
| # tests are gated to Linux or Windows, and the comma-decimal- | |
| # locale test typically skips. Budget 35 matches that set. | |
| SKIPPED=$(grep -oE '^Tests skipped[[:space:]]*:[[:space:]]*[0-9]+' /tmp/test-output.txt | grep -oE '[0-9]+' | head -1) | |
| SKIPPED=${SKIPPED:-0} | |
| echo "Tests skipped: $SKIPPED (ceiling 35)" | |
| if [ "$SKIPPED" -gt 35 ]; then | |
| echo "::error::Skip count $SKIPPED exceeds ceiling 35 on PHP ${PHP_VERSION} - macOS — a capability likely vanished; tests are hiding in the skip counter" | |
| grep -E '^SKIP\b' /tmp/test-output.txt || true | |
| exit 1 | |
| fi |