Skip to content

build(deps): bump actions/upload-artifact from 4.6.2 to 7.0.1 #183

build(deps): bump actions/upload-artifact from 4.6.2 to 7.0.1

build(deps): bump actions/upload-artifact from 4.6.2 to 7.0.1 #183

Workflow file for this run

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