From 9417d64bb81ec2f6a966896aff5a2298f2290100 Mon Sep 17 00:00:00 2001 From: Benjamin Fahl Date: Fri, 8 May 2026 20:54:39 +0200 Subject: [PATCH 1/5] docs: add php-fpm setup guide and packages reference (#25) Add Packages table covering cli/fpm/micro with links to upstream static-php.dev SAPI Reference, and a Running PHP-FPM section with a minimal config, foreground run, systemd user unit, launchd plist, and nginx FastCGI block. Closes #25. --- README.md | 167 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) diff --git a/README.md b/README.md index ec7f169..5cfec9c 100644 --- a/README.md +++ b/README.md @@ -77,3 +77,170 @@ pvm init ### Auto-Switching If you run `pvm init` or manually create a `.php-version` file in a project directory containing `8.3`, PVM will automatically switch to your best local `8.3.x` patch when you `cd` into that folder. The `cd` hook is installed via `pvm env` (Bash, Zsh, or Fish — auto-detected from `$SHELL`). + +## Packages + +Each PHP version can ship up to three binaries; you pick which during `pvm install` via the MultiSelect prompt. All land under `$PVM_DIR/versions//bin/`. Upstream reference for all three SAPIs: [static-php.dev — SAPI Reference](https://static-php.dev/en/guide/sapi-reference.html). + +| Package | Binary | What it is | +|---------|--------|------------| +| `cli` (default) | `php` | Standard command-line PHP — runs scripts, REPL via `php -a`, drives Composer. See [SAPI Reference: CLI](https://static-php.dev/en/guide/sapi-reference.html#cli). | +| `fpm` | `php-fpm` | FastCGI Process Manager for serving PHP behind nginx/Caddy/Apache. Setup details in the next section + [SAPI Reference: FPM](https://static-php.dev/en/guide/sapi-reference.html#fpm). | +| `micro` | `micro.sfx` | [phpmicro](https://github.com/easysoft/phpmicro) self-contained executable stub — concat with a `.php` or `.phar` to ship a single-file PHP app. Combining requires the upstream [`spc`](https://static-php.dev/en/guide/getting-started.html) toolchain (`spc micro:combine app.phar --output=app`); pvm only delivers the stub. See [SAPI Reference: Micro](https://static-php.dev/en/guide/sapi-reference.html#micro). | + +After `pvm use `, every selected binary is on `$PATH` (CLI as `php`, FPM as `php-fpm`); `micro.sfx` stays at its absolute path since it's a build artifact, not something you invoke directly. + +## Running PHP-FPM + +> Upstream reference: [static-php.dev — SAPI Reference: FPM](https://static-php.dev/en/guide/sapi-reference.html#fpm) documents the binary's CLI flags (`-y`, `-c`, `-t`), a minimal `php-fpm.conf`, and an nginx FastCGI block. The guide below extends that with service wiring (systemd / launchd) and pvm-specific paths. + +PVM downloads a static `php-fpm` binary alongside `php` when you tick the `fpm` package during `pvm install`. The static-php-cli tarball ships only the binary — no `php-fpm.conf`, no pool files, no init script — so you wire those up yourself. The binary lives next to the CLI at: + +``` +$PVM_DIR/versions//bin/php-fpm +``` + +`$PVM_DIR` defaults to `~/.local/share/pvm`. After running `pvm use 8.4` it is also on `$PATH` as plain `php-fpm`. + +### 1. Install the fpm package + +```bash +pvm install 8.4 +# When the MultiSelect prompt appears, tick "fpm" (and "cli" if you want both). +pvm use 8.4 +php-fpm -v # confirm it resolves to the pvm-managed binary +which php-fpm # → ~/.local/share/pvm/versions/8.4.x/bin/php-fpm +``` + +### 2. Create a minimal config + +Put these under `~/.config/php-fpm/` (any path works — the binary takes `-y` and `-c`): + +`~/.config/php-fpm/php-fpm.conf`: + +```ini +[global] +pid = /tmp/php-fpm.pid +error_log = /tmp/php-fpm.log +daemonize = no + +include = /home/YOU/.config/php-fpm/pool.d/*.conf +``` + +`~/.config/php-fpm/pool.d/www.conf`: + +```ini +[www] +user = YOU +group = YOU +listen = 127.0.0.1:9000 +; or a unix socket: +; listen = /tmp/php-fpm-www.sock +; listen.owner = YOU +; listen.group = YOU +; listen.mode = 0660 + +pm = dynamic +pm.max_children = 5 +pm.start_servers = 2 +pm.min_spare_servers = 1 +pm.max_spare_servers = 3 + +catch_workers_output = yes +clear_env = no +``` + +Replace `YOU` with your username (`whoami`). + +### 3. Run it in the foreground + +```bash +# Validate config first +php-fpm -y ~/.config/php-fpm/php-fpm.conf -t + +# Foreground run, logs to stdout +php-fpm -y ~/.config/php-fpm/php-fpm.conf -F + +# With a custom php.ini (the static binary has no compiled-in ini path) +php-fpm -c ~/.config/php-fpm/php.ini -y ~/.config/php-fpm/php-fpm.conf -F +``` + +Flag summary (matches upstream [SAPI Reference: FPM](https://static-php.dev/en/guide/sapi-reference.html#fpm)): + +- `-y ` — `php-fpm.conf` path (required, no default for static builds) +- `-c ` — `php.ini` path (optional; without it, fpm runs with hard-coded defaults) +- `-t` — validate config and exit +- `-F` — stay in foreground (don't fork to daemon) +- `-v` — print version +- `-m` — list compiled-in extensions + +### 4. Run it as a service + +**systemd (Linux, user unit)** — `~/.config/systemd/user/php-fpm.service`: + +```ini +[Unit] +Description=PHP-FPM (managed by pvm) +After=network.target + +[Service] +Type=simple +ExecStart=%h/.local/share/pvm/versions/8.4.18/bin/php-fpm -y %h/.config/php-fpm/php-fpm.conf -F +Restart=on-failure + +[Install] +WantedBy=default.target +``` + +```bash +systemctl --user daemon-reload +systemctl --user enable --now php-fpm +journalctl --user -u php-fpm -f +``` + +Pin the full semver in `ExecStart` (e.g. `8.4.18`) — symlinking to `versions/8.4` is not maintained by pvm, so a future `pvm install 8.4` that resolves to `8.4.19` will not move the service. + +**launchd (macOS)** — `~/Library/LaunchAgents/dev.pvm.php-fpm.plist`: + +```xml + + + + + Labeldev.pvm.php-fpm + ProgramArguments + + /Users/YOU/.local/share/pvm/versions/8.4.18/bin/php-fpm + -y + /Users/YOU/.config/php-fpm/php-fpm.conf + -F + + RunAtLoad + KeepAlive + StandardOutPath/tmp/php-fpm.out.log + StandardErrorPath/tmp/php-fpm.err.log + + +``` + +```bash +launchctl load ~/Library/LaunchAgents/dev.pvm.php-fpm.plist +``` + +### 5. Hook up nginx + +```nginx +location ~ \.php$ { + fastcgi_pass 127.0.0.1:9000; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; +} +``` + +### Notes + +- The static binary is self-contained — no system libphp / no extension `.so` files. Run `php-fpm -m` to list the extensions baked into your build. +- Switching the active CLI via `pvm use 8.3` does **not** restart your fpm service; the service runs whichever absolute path you wired into the unit/plist. Bump the path and reload when you upgrade. +- For multiple parallel versions (e.g. 8.3 + 8.4), run two services on different ports/sockets — `pvm` does not multiplex fpm for you. From 4da33751f3cff6876a3a799c59a6ccd328659ed7 Mon Sep 17 00:00:00 2001 From: Benjamin Fahl Date: Fri, 8 May 2026 21:11:18 +0200 Subject: [PATCH 2/5] test(e2e): walk the README PHP-FPM setup end-to-end on each PR Adds tests/e2e/fpm-readme.sh and a new e2e-fpm job in release.yml chained between tests and release, so the README docs cannot drift from the static-php-cli FPM tarball pvm distributes without breaking CI. The script downloads the latest static fpm tarball directly (bypassing pvm's interactive MultiSelect), drops it into the pvm versions layout, and verifies: - pvm ls discovers the version - the documented php-fpm.conf and pool.d/www.conf pass php-fpm -t - php-fpm -F listens on 127.0.0.1:9000 with the documented worker count - php-fpm -c custom.ini -y conf -t works - php-fpm -v / -m emit the expected output The release job now needs [tests, e2e-fpm], so a README change that breaks the documented flow blocks the release. Verified locally in a fresh ubuntu:24.04 container against both the install.sh path and a from-source build of pvm. --- .github/workflows/release.yml | 37 +++++++- tests/e2e/fpm-readme.sh | 167 ++++++++++++++++++++++++++++++++++ 2 files changed, 203 insertions(+), 1 deletion(-) create mode 100755 tests/e2e/fpm-readme.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0128f56..1cc4396 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,9 +34,44 @@ jobs: - name: Run Tests run: cargo test + e2e-fpm: + name: E2E PHP-FPM (README walkthrough) + needs: tests + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 + + - name: Install runtime deps + run: sudo apt-get update && sudo apt-get install -y --no-install-recommends netcat-openbsd + + - name: Build pvm (release) + run: cargo build --release + + - name: Run E2E FPM README test + env: + PVM_BIN: ${{ github.workspace }}/target/release/pvm + run: bash tests/e2e/fpm-readme.sh + + - name: Upload fpm logs on failure + if: failure() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: fpm-logs + path: | + /tmp/fpm.stdout + /tmp/fpm.stderr + if-no-files-found: ignore + release: name: Semantic Release - needs: tests + needs: [tests, e2e-fpm] runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' permissions: diff --git a/tests/e2e/fpm-readme.sh b/tests/e2e/fpm-readme.sh new file mode 100755 index 0000000..78d1ce8 --- /dev/null +++ b/tests/e2e/fpm-readme.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env bash +# End-to-end test for the "Running PHP-FPM" section of README.md. +# +# Validates that the documented config samples and CLI flags actually work +# against the static-php-cli FPM tarball that pvm distributes. +# +# Usage: +# PVM_BIN=/path/to/pvm bash tests/e2e/fpm-readme.sh # use a pre-built pvm (CI) +# bash tests/e2e/fpm-readme.sh # download via install.sh +# +# Optional env: +# PVM_VERSION_FILTER major.minor to install (default: 8.4) +set -euo pipefail + +GREEN='\033[0;32m'; RED='\033[0;31m'; BLUE='\033[0;34m'; YEL='\033[0;33m'; NC='\033[0m' +ok() { echo -e "${GREEN}✓${NC} $*"; } +fail() { echo -e "${RED}✗${NC} $*" >&2; exit 1; } +step() { echo -e "\n${BLUE}==>${NC} $*"; } +warn() { echo -e "${YEL}!${NC} $*"; } + +for tool in curl tar nc; do + command -v "$tool" >/dev/null 2>&1 || fail "missing required tool: $tool" +done + +VERSION_FILTER="${PVM_VERSION_FILTER:-8.4}" + +# --------------------------------------------------------------------------- +step "1. Stage pvm binary" +if [[ -n "${PVM_BIN:-}" ]]; then + [[ -x "$PVM_BIN" ]] || fail "PVM_BIN set but not executable: $PVM_BIN" + : "${PVM_DIR:=$HOME/.local/share/pvm}" + mkdir -p "$PVM_DIR/bin" + cp "$PVM_BIN" "$PVM_DIR/bin/pvm" + PVM_BIN="$PVM_DIR/bin/pvm" + ok "using pre-built pvm at $PVM_BIN" +else + curl -fsSL https://raw.githubusercontent.com/WebProject-xyz/php-version-manager/main/install.sh | bash + PVM_BIN="$HOME/.local/share/pvm/bin/pvm" + ok "installed via install.sh" +fi +"$PVM_BIN" --version + +# --------------------------------------------------------------------------- +step "2. Download php-fpm static binary (bypass interactive MultiSelect)" +ARCH="$(uname -m)" +case "$ARCH" in + x86_64|amd64) TGT="linux-x86_64" ;; + aarch64|arm64) TGT="linux-aarch64" ;; + *) fail "unsupported arch $ARCH" ;; +esac + +INDEX_URL="https://dl.static-php.dev/static-php-cli/bulk/?format=json" +ESCAPED_FILTER="${VERSION_FILTER//./\\.}" +LATEST_FPM=$(curl -fsSL "$INDEX_URL" \ + | grep -o "\"php-${ESCAPED_FILTER}\\.[0-9]*-fpm-${TGT}\\.tar\\.gz\"" \ + | sort -V | tail -n 1 | tr -d '"') +[[ -n "$LATEST_FPM" ]] || fail "no php-${VERSION_FILTER}.x fpm tarball for $TGT" +RESOLVED_VER=$(echo "$LATEST_FPM" \ + | sed -E "s/php-(${ESCAPED_FILTER}\\.[0-9]+)-fpm-${TGT}\\.tar\\.gz/\\1/") +ok "resolved $LATEST_FPM (version $RESOLVED_VER)" + +DEST="$HOME/.local/share/pvm/versions/$RESOLVED_VER/bin" +mkdir -p "$DEST" +curl -fsSL "https://dl.static-php.dev/static-php-cli/bulk/$LATEST_FPM" \ + | tar -xzf - -C "$DEST" +chmod 0755 "$DEST"/* +[[ -x "$DEST/php-fpm" ]] || fail "php-fpm not extracted to $DEST" +ok "php-fpm extracted to $DEST/php-fpm" + +# --------------------------------------------------------------------------- +step "3. pvm sees the new version" +"$PVM_BIN" ls +"$PVM_BIN" ls | grep -q "$RESOLVED_VER" \ + && ok "pvm ls includes $RESOLVED_VER" \ + || fail "pvm ls did not list $RESOLVED_VER" + +# --------------------------------------------------------------------------- +step "4. README §3 — flags: -v, -m" +"$DEST/php-fpm" -v && ok "php-fpm -v works" || fail "php-fpm -v failed" +MODULES_OUT=$("$DEST/php-fpm" -m) +echo "$MODULES_OUT" | head -n 20 +MOD_COUNT=$(echo "$MODULES_OUT" | grep -cE '^[a-z]' || true) +ok "php-fpm -m listed $MOD_COUNT modules" + +# --------------------------------------------------------------------------- +step "5. README §2 — write minimal config" +USER_NAME="$(whoami)" +mkdir -p "$HOME/.config/php-fpm/pool.d" + +cat > "$HOME/.config/php-fpm/php-fpm.conf" < "$HOME/.config/php-fpm/pool.d/www.conf" < /tmp/fpm.stdout 2> /tmp/fpm.stderr & +FPM_PID=$! +trap 'kill $FPM_PID 2>/dev/null || true' EXIT + +for i in $(seq 1 50); do + if nc -z 127.0.0.1 9000 2>/dev/null; then + ok "php-fpm listening on 127.0.0.1:9000 (after ${i}*100ms)" + break + fi + sleep 0.1 +done + +if ! nc -z 127.0.0.1 9000 2>/dev/null; then + echo "--- stdout ---"; cat /tmp/fpm.stdout || true + echo "--- stderr ---"; cat /tmp/fpm.stderr || true + fail "php-fpm did not listen on :9000 within 5s" +fi + +WORKER_COUNT=$(pgrep -P "$FPM_PID" 2>/dev/null | wc -l || echo 0) +[[ "$WORKER_COUNT" -ge 1 ]] && ok "$WORKER_COUNT worker(s) spawned" \ + || warn "no workers detected" + +kill -QUIT "$FPM_PID" 2>/dev/null || true +wait "$FPM_PID" 2>/dev/null || true +ok "php-fpm shutdown clean" + +# --------------------------------------------------------------------------- +step "8. README §3 — custom php.ini via -c flag" +cat > "$HOME/.config/php-fpm/php.ini" <<'EOF' +memory_limit = 256M +expose_php = Off +EOF +"$DEST/php-fpm" -c "$HOME/.config/php-fpm/php.ini" \ + -y "$HOME/.config/php-fpm/php-fpm.conf" -t \ + && ok "php-fpm -c custom-ini -y conf -t works" \ + || fail "php-fpm with -c failed" + +# --------------------------------------------------------------------------- +echo +echo -e "${GREEN}All README PHP-FPM steps validated end-to-end.${NC}" +echo "Resolved version: $RESOLVED_VER" +echo "Binary path: $DEST/php-fpm" +echo "Config files: $HOME/.config/php-fpm/" From 7cee3fbbd68cae78f6900480f009ce92e8ab3c4c Mon Sep 17 00:00:00 2001 From: Benjamin Fahl Date: Fri, 8 May 2026 22:04:26 +0200 Subject: [PATCH 3/5] test(e2e): split fpm-readme into per-case scripts and broaden coverage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor the monolithic fpm-readme.sh into: tests/e2e/run.sh driver tests/e2e/_lib.sh shared helpers (run_under_expect, fcgi_call) tests/e2e/cases/NN_*.sh one file per case The driver runs each case in a fresh bash subprocess, so state from one case can no longer hide bugs in the next (the previous monolith silently passed because pvm's 24h .update_check_guard suppressed the patch-update prompt for any case after the first one to use it). Coverage now includes (Linux, ubuntu-latest): 01 real `pvm install` flow with interactive MultiSelect (expect) 02 pvm ls discovers [cli, fpm] 03 pvm use through shell wrapper switches PATH; which php-fpm 04 pvm current after pvm use 05 .php-version cd-hook auto-switch 06 pvm use → install prompt + decline (#24) 07 patch-update detection on pvm use 08 README php-fpm.conf + pool.d/{www,sock}.conf passes -t 09 fpm -F listens on TCP :9000 + unix /tmp/php-fpm-www.sock 10 FastCGI roundtrip over TCP returns FCGI_OK 11 FastCGI roundtrip over unix socket returns FCGI_OK 12 -c php.ini effective inside worker (memory_limit, expose_php) 13 pid file + error log written 14 SIGQUIT clean shutdown 15 pvm uninstall removes the version directory CI job in release.yml renamed to "e2e tests - linux", chained between the existing `tests` and `release` jobs. apt-get installs expect and libfcgi-bin (for cgi-fcgi) on top of netcat-openbsd. The suite targets PHP 8.5 by default (PVM_VERSION_MAJOR_MINOR=8.5), overridable per env. --- .github/workflows/release.yml | 9 +- tests/e2e/_lib.sh | 43 ++++++ tests/e2e/cases/01_install.sh | 35 +++++ tests/e2e/cases/02_ls.sh | 11 ++ tests/e2e/cases/03_use_wrapper.sh | 30 +++++ tests/e2e/cases/04_current.sh | 24 ++++ tests/e2e/cases/05_php_version_hook.sh | 32 +++++ tests/e2e/cases/06_use_missing.sh | 25 ++++ tests/e2e/cases/07_patch_update.sh | 32 +++++ tests/e2e/cases/08_fpm_config.sh | 64 +++++++++ tests/e2e/cases/09_fpm_run.sh | 37 ++++++ tests/e2e/cases/10_fcgi_tcp.sh | 10 ++ tests/e2e/cases/11_fcgi_sock.sh | 10 ++ tests/e2e/cases/12_php_ini_effective.sh | 15 +++ tests/e2e/cases/13_pid_log.sh | 16 +++ tests/e2e/cases/14_fpm_shutdown.sh | 25 ++++ tests/e2e/cases/15_uninstall.sh | 18 +++ tests/e2e/fpm-readme.sh | 167 ------------------------ tests/e2e/run.sh | 122 +++++++++++++++++ 19 files changed, 554 insertions(+), 171 deletions(-) create mode 100755 tests/e2e/_lib.sh create mode 100755 tests/e2e/cases/01_install.sh create mode 100755 tests/e2e/cases/02_ls.sh create mode 100755 tests/e2e/cases/03_use_wrapper.sh create mode 100755 tests/e2e/cases/04_current.sh create mode 100755 tests/e2e/cases/05_php_version_hook.sh create mode 100755 tests/e2e/cases/06_use_missing.sh create mode 100755 tests/e2e/cases/07_patch_update.sh create mode 100755 tests/e2e/cases/08_fpm_config.sh create mode 100755 tests/e2e/cases/09_fpm_run.sh create mode 100755 tests/e2e/cases/10_fcgi_tcp.sh create mode 100755 tests/e2e/cases/11_fcgi_sock.sh create mode 100755 tests/e2e/cases/12_php_ini_effective.sh create mode 100755 tests/e2e/cases/13_pid_log.sh create mode 100755 tests/e2e/cases/14_fpm_shutdown.sh create mode 100755 tests/e2e/cases/15_uninstall.sh delete mode 100755 tests/e2e/fpm-readme.sh create mode 100755 tests/e2e/run.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1cc4396..f1ae012 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,7 +35,7 @@ jobs: run: cargo test e2e-fpm: - name: E2E PHP-FPM (README walkthrough) + name: e2e tests - linux needs: tests runs-on: ubuntu-latest steps: @@ -49,15 +49,16 @@ jobs: uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - name: Install runtime deps - run: sudo apt-get update && sudo apt-get install -y --no-install-recommends netcat-openbsd + run: sudo apt-get update && sudo apt-get install -y --no-install-recommends netcat-openbsd expect libfcgi-bin - name: Build pvm (release) run: cargo build --release - - name: Run E2E FPM README test + - name: Run E2E suite env: PVM_BIN: ${{ github.workspace }}/target/release/pvm - run: bash tests/e2e/fpm-readme.sh + PVM_VERSION_MAJOR_MINOR: '8.5' + run: bash tests/e2e/run.sh - name: Upload fpm logs on failure if: failure() diff --git a/tests/e2e/_lib.sh b/tests/e2e/_lib.sh new file mode 100755 index 0000000..c36e3a3 --- /dev/null +++ b/tests/e2e/_lib.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# Shared helpers for tests/e2e cases. +# Source from a case via: source "$(dirname "$0")/../_lib.sh" +# The driver pre-exports PVM_BIN, PREVIOUS, LATEST, VFILTER, VDIR, etc. + +GREEN='\033[0;32m'; RED='\033[0;31m'; BLUE='\033[0;34m'; YEL='\033[0;33m'; NC='\033[0m' +ok() { echo -e "${GREEN}✓${NC} $*"; } +fail() { echo -e "${RED}✗${NC} $*" >&2; exit 1; } +step() { echo -e "${BLUE}==>${NC} $*"; } +warn() { echo -e "${YEL}!${NC} $*"; } + +# Spawn a script under expect, auto-decline the three known prompts. +# Always exits 0; caller asserts on captured stdout for markers. +run_under_expect() { + local script="$1" + expect < body to fpm via cgi-fcgi and echo the response. +# Args: connect_target (e.g. "127.0.0.1:9000" or "/tmp/sock"), php_body +fcgi_call() { + local connect="$1" + local php_body="$2" + local script_dir + script_dir=$(mktemp -d) + echo " "$script_dir/run.php" + SCRIPT_FILENAME="$script_dir/run.php" \ + SCRIPT_NAME=/run.php \ + REQUEST_METHOD=GET \ + QUERY_STRING="" \ + cgi-fcgi -bind -connect "$connect" + rm -rf "$script_dir" +} diff --git a/tests/e2e/cases/01_install.sh b/tests/e2e/cases/01_install.sh new file mode 100755 index 0000000..a4b045f --- /dev/null +++ b/tests/e2e/cases/01_install.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Real `pvm install` flow with interactive MultiSelect via expect. +# Toggles `fpm` on top of the default `cli` selection. +set -euo pipefail +source "$(dirname "$0")/../_lib.sh" + +step "pvm install $PREVIOUS — toggle fpm in MultiSelect" + +expect </dev/null \ + && ok "pvm ls shows [cli, fpm] for $PREVIOUS" \ + || warn "pvm ls did not show both [cli, fpm] tags" diff --git a/tests/e2e/cases/03_use_wrapper.sh b/tests/e2e/cases/03_use_wrapper.sh new file mode 100755 index 0000000..0756642 --- /dev/null +++ b/tests/e2e/cases/03_use_wrapper.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# `pvm use ` through the shell wrapper switches PATH. +set -euo pipefail +source "$(dirname "$0")/../_lib.sh" + +step "pvm use $VFILTER via wrapper — PATH switch + which php-fpm" + +WORKDIR=$(mktemp -d) +trap 'rm -rf "$WORKDIR"' EXIT + +cat > "$WORKDIR/run.sh" <&1 | head -n 1 +EOF +chmod +x "$WORKDIR/run.sh" + +OUT=$(run_under_expect "$WORKDIR/run.sh" 2>&1) +echo "$OUT" + +echo "$OUT" | grep -q "PATH_AFTER_USE=$VDIR" \ + || fail "PATH not switched to $VDIR" +echo "$OUT" | grep -q "WHICH_PHP_FPM=$VDIR/php-fpm" \ + || fail "which php-fpm did not resolve under pvm versions dir" +ok "wrapper switched PATH; php-fpm → $VDIR/php-fpm" diff --git a/tests/e2e/cases/04_current.sh b/tests/e2e/cases/04_current.sh new file mode 100755 index 0000000..fa073da --- /dev/null +++ b/tests/e2e/cases/04_current.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# `pvm current` reports the active version after `pvm use`. +set -euo pipefail +source "$(dirname "$0")/../_lib.sh" + +step "pvm current — after pvm use" + +WORKDIR=$(mktemp -d) +trap 'rm -rf "$WORKDIR"' EXIT + +cat > "$WORKDIR/run.sh" </dev/null +pvm current +EOF +chmod +x "$WORKDIR/run.sh" + +OUT=$(run_under_expect "$WORKDIR/run.sh" 2>&1) +echo "$OUT" +echo "$OUT" | grep -q "$PREVIOUS" || fail "pvm current did not show $PREVIOUS" +ok "pvm current → $PREVIOUS" diff --git a/tests/e2e/cases/05_php_version_hook.sh b/tests/e2e/cases/05_php_version_hook.sh new file mode 100755 index 0000000..d4f2fa9 --- /dev/null +++ b/tests/e2e/cases/05_php_version_hook.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# `.php-version` cd-hook auto-switches the active version. +set -euo pipefail +source "$(dirname "$0")/../_lib.sh" + +step ".php-version cd-hook" + +WORKDIR=$(mktemp -d) +trap 'rm -rf "$WORKDIR"' EXIT + +PROJ="$WORKDIR/proj" +mkdir -p "$PROJ" +echo "$VFILTER" > "$PROJ/.php-version" + +cat > "$WORKDIR/run.sh" </dev/null || { echo "_pvm_cd_hook not defined"; exit 1; } +cd '$PROJ' +_pvm_cd_hook +pvm current +EOF +chmod +x "$WORKDIR/run.sh" + +OUT=$(run_under_expect "$WORKDIR/run.sh" 2>&1) +echo "$OUT" +echo "$OUT" | grep -q "$PREVIOUS" \ + || fail ".php-version did not switch to $VFILTER → $PREVIOUS" +ok ".php-version cd-hook switched to $PREVIOUS" diff --git a/tests/e2e/cases/06_use_missing.sh b/tests/e2e/cases/06_use_missing.sh new file mode 100755 index 0000000..c57a352 --- /dev/null +++ b/tests/e2e/cases/06_use_missing.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# `pvm use ` prompts to install (#24); decline cancels cleanly. +set -euo pipefail +source "$(dirname "$0")/../_lib.sh" + +step "pvm use $MISSING_VER (uninstalled) → install prompt → decline" + +OUT=$(expect <&1 +set timeout 60 +log_user 1 +spawn $PVM_BIN use $MISSING_VER +expect { + -re {is not installed locally.*Do you want to install} { send "n\r" } + timeout { puts "TIMEOUT_MISSING_PROMPT" } + eof { puts "EOF_MISSING_PROMPT" } +} +expect eof +EXPECT_EOF +) +echo "$OUT" +echo "$OUT" | grep -q "is not installed locally" \ + || fail "missing-version install prompt not shown" +echo "$OUT" | grep -q "Operation cancelled" \ + || fail "decline did not cancel cleanly" +ok "missing-version prompt + decline path works (#24)" diff --git a/tests/e2e/cases/07_patch_update.sh b/tests/e2e/cases/07_patch_update.sh new file mode 100755 index 0000000..e98f6b8 --- /dev/null +++ b/tests/e2e/cases/07_patch_update.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# Patch-update detection: `pvm use ` offers the newer patch. +set -euo pipefail +source "$(dirname "$0")/../_lib.sh" + +if [[ "$LATEST" == "$PREVIOUS" ]]; then + warn "only one ${VFILTER}.x patch upstream — skipping patch-update detection" + exit 0 +fi + +# pvm rate-limits the patch-update check to once per 24h via this guard file; +# clear it so the prompt actually fires when this case runs after another `pvm use`. +rm -f "$HOME/.local/share/pvm/.update_check_guard" + +step "pvm use $VFILTER offers $LATEST over $PREVIOUS" + +OUT=$(expect <&1 +set timeout 90 +log_user 1 +spawn $PVM_BIN use $VFILTER +expect { + -re {patch version is available.*Do you want to install} { send "n\r" } + timeout { puts "TIMEOUT_PATCH_PROMPT" } + eof { puts "EOF_PATCH_PROMPT" } +} +expect eof +EXPECT_EOF +) +echo "$OUT" +echo "$OUT" | grep -q "$LATEST" \ + || fail "patch-update prompt did not mention $LATEST" +ok "patch-update prompt offered $PREVIOUS → $LATEST" diff --git a/tests/e2e/cases/08_fpm_config.sh b/tests/e2e/cases/08_fpm_config.sh new file mode 100755 index 0000000..0b8636f --- /dev/null +++ b/tests/e2e/cases/08_fpm_config.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# Write README config (TCP + Unix socket pools) and validate via `php-fpm -t`. +set -euo pipefail +source "$(dirname "$0")/../_lib.sh" + +step "Write README php-fpm config and validate via -t" + +USER_NAME="$(whoami)" +mkdir -p "$HOME/.config/php-fpm/pool.d" + +cat > "$HOME/.config/php-fpm/php-fpm.conf" < "$HOME/.config/php-fpm/pool.d/www.conf" < "$HOME/.config/php-fpm/pool.d/sock.conf" < "$HOME/.config/php-fpm/php.ini" <<'EOF' +memory_limit = 256M +expose_php = Off +EOF + +ok "wrote php-fpm.conf, pool.d/{www,sock}.conf, php.ini" + +"$VDIR/php-fpm" -y "$HOME/.config/php-fpm/php-fpm.conf" -t \ + && ok "php-fpm -t OK" \ + || fail "php-fpm -t failed" + +"$VDIR/php-fpm" -v +MOD_COUNT=$("$VDIR/php-fpm" -m | grep -cE '^[a-z]' || true) +ok "php-fpm -m listed $MOD_COUNT modules" diff --git a/tests/e2e/cases/09_fpm_run.sh b/tests/e2e/cases/09_fpm_run.sh new file mode 100755 index 0000000..c028fa3 --- /dev/null +++ b/tests/e2e/cases/09_fpm_run.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# Start php-fpm in foreground; verify TCP + unix socket listeners come up. +# Leaves the master running for fcgi roundtrip cases; driver kills on exit. +set -euo pipefail +source "$(dirname "$0")/../_lib.sh" + +step "Start php-fpm -F; verify listeners" + +rm -f "$FPM_PID_FILE" "$FPM_LOG_FILE" "$FPM_SOCK" + +"$VDIR/php-fpm" \ + -c "$HOME/.config/php-fpm/php.ini" \ + -y "$HOME/.config/php-fpm/php-fpm.conf" \ + -F \ + > /tmp/fpm.stdout 2> /tmp/fpm.stderr & + +FPM_PID=$! +echo "$FPM_PID" > "$E2E_STATE/fpm.pid" + +for i in $(seq 1 60); do + if nc -z 127.0.0.1 9000 2>/dev/null && [[ -S "$FPM_SOCK" ]]; then + ok "fpm listening on TCP :9000 + unix $FPM_SOCK (after ${i}*100ms)" + break + fi + sleep 0.1 +done + +if ! nc -z 127.0.0.1 9000 2>/dev/null; then + echo "--- stdout ---"; cat /tmp/fpm.stdout || true + echo "--- stderr ---"; cat /tmp/fpm.stderr || true + fail "TCP :9000 not listening" +fi +[[ -S "$FPM_SOCK" ]] || fail "unix socket $FPM_SOCK not created" + +WORKER_COUNT=$(pgrep -P "$FPM_PID" 2>/dev/null | wc -l || echo 0) +[[ "$WORKER_COUNT" -ge 2 ]] && ok "$WORKER_COUNT workers spawned" \ + || warn "only $WORKER_COUNT workers detected" diff --git a/tests/e2e/cases/10_fcgi_tcp.sh b/tests/e2e/cases/10_fcgi_tcp.sh new file mode 100755 index 0000000..e48a900 --- /dev/null +++ b/tests/e2e/cases/10_fcgi_tcp.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# FastCGI roundtrip over TCP — execute . +set -euo pipefail +source "$(dirname "$0")/../_lib.sh" + +step "FastCGI roundtrip over TCP 127.0.0.1:9000" +RESPONSE=$(fcgi_call "127.0.0.1:9000" 'echo "FCGI_OK\n";' 2>&1) +echo "$RESPONSE" +echo "$RESPONSE" | grep -q "FCGI_OK" || fail "TCP roundtrip did not return FCGI_OK" +ok "TCP FastCGI executed PHP" diff --git a/tests/e2e/cases/11_fcgi_sock.sh b/tests/e2e/cases/11_fcgi_sock.sh new file mode 100755 index 0000000..26186d2 --- /dev/null +++ b/tests/e2e/cases/11_fcgi_sock.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# FastCGI roundtrip over Unix socket. +set -euo pipefail +source "$(dirname "$0")/../_lib.sh" + +step "FastCGI roundtrip over $FPM_SOCK" +RESPONSE=$(fcgi_call "$FPM_SOCK" 'echo "FCGI_OK\n";' 2>&1) +echo "$RESPONSE" +echo "$RESPONSE" | grep -q "FCGI_OK" || fail "unix socket roundtrip failed" +ok "Unix socket FastCGI executed PHP" diff --git a/tests/e2e/cases/12_php_ini_effective.sh b/tests/e2e/cases/12_php_ini_effective.sh new file mode 100755 index 0000000..5138df9 --- /dev/null +++ b/tests/e2e/cases/12_php_ini_effective.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# Custom php.ini via -c flag — assert ini_get values inside the FPM worker. +set -euo pipefail +source "$(dirname "$0")/../_lib.sh" + +step "-c php.ini effective inside worker (memory_limit, expose_php)" +RESPONSE=$(fcgi_call "127.0.0.1:9000" \ + 'echo "MEM=" . ini_get("memory_limit") . "\n"; echo "EXPOSE=" . (ini_get("expose_php") ? "On" : "Off") . "\n";' \ + 2>&1) +echo "$RESPONSE" +echo "$RESPONSE" | grep -q "MEM=256M" \ + || fail "memory_limit not 256M inside worker — -c flag not effective" +echo "$RESPONSE" | grep -q "EXPOSE=Off" \ + || fail "expose_php not Off inside worker" +ok "php.ini applied inside worker" diff --git a/tests/e2e/cases/13_pid_log.sh b/tests/e2e/cases/13_pid_log.sh new file mode 100755 index 0000000..502c6af --- /dev/null +++ b/tests/e2e/cases/13_pid_log.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Pid file written + error log present. +set -euo pipefail +source "$(dirname "$0")/../_lib.sh" + +step "pid file + error log" + +[[ -s "$FPM_PID_FILE" ]] || fail "pid file $FPM_PID_FILE not written" +PID_IN_FILE=$(cat "$FPM_PID_FILE") +EXPECTED_PID=$(cat "$E2E_STATE/fpm.pid") +[[ "$PID_IN_FILE" -eq "$EXPECTED_PID" ]] \ + && ok "pid file matches master ($PID_IN_FILE)" \ + || warn "pid file $PID_IN_FILE != master $EXPECTED_PID" + +[[ -s "$FPM_LOG_FILE" ]] && ok "error log $FPM_LOG_FILE has content" \ + || warn "error log empty (ok if startup was quiet)" diff --git a/tests/e2e/cases/14_fpm_shutdown.sh b/tests/e2e/cases/14_fpm_shutdown.sh new file mode 100755 index 0000000..3cffa30 --- /dev/null +++ b/tests/e2e/cases/14_fpm_shutdown.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Send SIGQUIT to the fpm master and wait for clean exit. +set -euo pipefail +source "$(dirname "$0")/../_lib.sh" + +step "SIGQUIT shutdown" + +if [[ ! -f "$E2E_STATE/fpm.pid" ]]; then + warn "no fpm pid recorded — assuming already stopped" + exit 0 +fi + +FPM_PID=$(cat "$E2E_STATE/fpm.pid") +kill -QUIT "$FPM_PID" 2>/dev/null || true + +for i in $(seq 1 50); do + if ! kill -0 "$FPM_PID" 2>/dev/null; then + ok "fpm master $FPM_PID exited cleanly (after ${i}*100ms)" + rm -f "$E2E_STATE/fpm.pid" + exit 0 + fi + sleep 0.1 +done + +fail "fpm master $FPM_PID still alive after 5s" diff --git a/tests/e2e/cases/15_uninstall.sh b/tests/e2e/cases/15_uninstall.sh new file mode 100755 index 0000000..cb08b69 --- /dev/null +++ b/tests/e2e/cases/15_uninstall.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# `pvm uninstall ` removes the version dir and pvm ls drops the entry. +# Driver runs this AFTER fpm has been shut down. +set -euo pipefail +source "$(dirname "$0")/../_lib.sh" + +step "pvm uninstall $PREVIOUS" + +"$PVM_BIN" uninstall "$PREVIOUS" + +[[ ! -d "$HOME/.local/share/pvm/versions/$PREVIOUS" ]] \ + && ok "versions/$PREVIOUS directory removed" \ + || fail "uninstall left versions/$PREVIOUS in place" + +if "$PVM_BIN" ls 2>&1 | grep -q "$PREVIOUS"; then + fail "pvm ls still shows $PREVIOUS after uninstall" +fi +ok "pvm ls no longer lists $PREVIOUS" diff --git a/tests/e2e/fpm-readme.sh b/tests/e2e/fpm-readme.sh deleted file mode 100755 index 78d1ce8..0000000 --- a/tests/e2e/fpm-readme.sh +++ /dev/null @@ -1,167 +0,0 @@ -#!/usr/bin/env bash -# End-to-end test for the "Running PHP-FPM" section of README.md. -# -# Validates that the documented config samples and CLI flags actually work -# against the static-php-cli FPM tarball that pvm distributes. -# -# Usage: -# PVM_BIN=/path/to/pvm bash tests/e2e/fpm-readme.sh # use a pre-built pvm (CI) -# bash tests/e2e/fpm-readme.sh # download via install.sh -# -# Optional env: -# PVM_VERSION_FILTER major.minor to install (default: 8.4) -set -euo pipefail - -GREEN='\033[0;32m'; RED='\033[0;31m'; BLUE='\033[0;34m'; YEL='\033[0;33m'; NC='\033[0m' -ok() { echo -e "${GREEN}✓${NC} $*"; } -fail() { echo -e "${RED}✗${NC} $*" >&2; exit 1; } -step() { echo -e "\n${BLUE}==>${NC} $*"; } -warn() { echo -e "${YEL}!${NC} $*"; } - -for tool in curl tar nc; do - command -v "$tool" >/dev/null 2>&1 || fail "missing required tool: $tool" -done - -VERSION_FILTER="${PVM_VERSION_FILTER:-8.4}" - -# --------------------------------------------------------------------------- -step "1. Stage pvm binary" -if [[ -n "${PVM_BIN:-}" ]]; then - [[ -x "$PVM_BIN" ]] || fail "PVM_BIN set but not executable: $PVM_BIN" - : "${PVM_DIR:=$HOME/.local/share/pvm}" - mkdir -p "$PVM_DIR/bin" - cp "$PVM_BIN" "$PVM_DIR/bin/pvm" - PVM_BIN="$PVM_DIR/bin/pvm" - ok "using pre-built pvm at $PVM_BIN" -else - curl -fsSL https://raw.githubusercontent.com/WebProject-xyz/php-version-manager/main/install.sh | bash - PVM_BIN="$HOME/.local/share/pvm/bin/pvm" - ok "installed via install.sh" -fi -"$PVM_BIN" --version - -# --------------------------------------------------------------------------- -step "2. Download php-fpm static binary (bypass interactive MultiSelect)" -ARCH="$(uname -m)" -case "$ARCH" in - x86_64|amd64) TGT="linux-x86_64" ;; - aarch64|arm64) TGT="linux-aarch64" ;; - *) fail "unsupported arch $ARCH" ;; -esac - -INDEX_URL="https://dl.static-php.dev/static-php-cli/bulk/?format=json" -ESCAPED_FILTER="${VERSION_FILTER//./\\.}" -LATEST_FPM=$(curl -fsSL "$INDEX_URL" \ - | grep -o "\"php-${ESCAPED_FILTER}\\.[0-9]*-fpm-${TGT}\\.tar\\.gz\"" \ - | sort -V | tail -n 1 | tr -d '"') -[[ -n "$LATEST_FPM" ]] || fail "no php-${VERSION_FILTER}.x fpm tarball for $TGT" -RESOLVED_VER=$(echo "$LATEST_FPM" \ - | sed -E "s/php-(${ESCAPED_FILTER}\\.[0-9]+)-fpm-${TGT}\\.tar\\.gz/\\1/") -ok "resolved $LATEST_FPM (version $RESOLVED_VER)" - -DEST="$HOME/.local/share/pvm/versions/$RESOLVED_VER/bin" -mkdir -p "$DEST" -curl -fsSL "https://dl.static-php.dev/static-php-cli/bulk/$LATEST_FPM" \ - | tar -xzf - -C "$DEST" -chmod 0755 "$DEST"/* -[[ -x "$DEST/php-fpm" ]] || fail "php-fpm not extracted to $DEST" -ok "php-fpm extracted to $DEST/php-fpm" - -# --------------------------------------------------------------------------- -step "3. pvm sees the new version" -"$PVM_BIN" ls -"$PVM_BIN" ls | grep -q "$RESOLVED_VER" \ - && ok "pvm ls includes $RESOLVED_VER" \ - || fail "pvm ls did not list $RESOLVED_VER" - -# --------------------------------------------------------------------------- -step "4. README §3 — flags: -v, -m" -"$DEST/php-fpm" -v && ok "php-fpm -v works" || fail "php-fpm -v failed" -MODULES_OUT=$("$DEST/php-fpm" -m) -echo "$MODULES_OUT" | head -n 20 -MOD_COUNT=$(echo "$MODULES_OUT" | grep -cE '^[a-z]' || true) -ok "php-fpm -m listed $MOD_COUNT modules" - -# --------------------------------------------------------------------------- -step "5. README §2 — write minimal config" -USER_NAME="$(whoami)" -mkdir -p "$HOME/.config/php-fpm/pool.d" - -cat > "$HOME/.config/php-fpm/php-fpm.conf" < "$HOME/.config/php-fpm/pool.d/www.conf" < /tmp/fpm.stdout 2> /tmp/fpm.stderr & -FPM_PID=$! -trap 'kill $FPM_PID 2>/dev/null || true' EXIT - -for i in $(seq 1 50); do - if nc -z 127.0.0.1 9000 2>/dev/null; then - ok "php-fpm listening on 127.0.0.1:9000 (after ${i}*100ms)" - break - fi - sleep 0.1 -done - -if ! nc -z 127.0.0.1 9000 2>/dev/null; then - echo "--- stdout ---"; cat /tmp/fpm.stdout || true - echo "--- stderr ---"; cat /tmp/fpm.stderr || true - fail "php-fpm did not listen on :9000 within 5s" -fi - -WORKER_COUNT=$(pgrep -P "$FPM_PID" 2>/dev/null | wc -l || echo 0) -[[ "$WORKER_COUNT" -ge 1 ]] && ok "$WORKER_COUNT worker(s) spawned" \ - || warn "no workers detected" - -kill -QUIT "$FPM_PID" 2>/dev/null || true -wait "$FPM_PID" 2>/dev/null || true -ok "php-fpm shutdown clean" - -# --------------------------------------------------------------------------- -step "8. README §3 — custom php.ini via -c flag" -cat > "$HOME/.config/php-fpm/php.ini" <<'EOF' -memory_limit = 256M -expose_php = Off -EOF -"$DEST/php-fpm" -c "$HOME/.config/php-fpm/php.ini" \ - -y "$HOME/.config/php-fpm/php-fpm.conf" -t \ - && ok "php-fpm -c custom-ini -y conf -t works" \ - || fail "php-fpm with -c failed" - -# --------------------------------------------------------------------------- -echo -echo -e "${GREEN}All README PHP-FPM steps validated end-to-end.${NC}" -echo "Resolved version: $RESOLVED_VER" -echo "Binary path: $DEST/php-fpm" -echo "Config files: $HOME/.config/php-fpm/" diff --git a/tests/e2e/run.sh b/tests/e2e/run.sh new file mode 100755 index 0000000..bef0545 --- /dev/null +++ b/tests/e2e/run.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env bash +# Driver for tests/e2e — runs each cases/NN_*.sh as a fresh bash subprocess +# so state from one case does not leak into the next. +# +# Usage: +# PVM_BIN=/path/to/pvm bash tests/e2e/run.sh # CI: use a pre-built pvm +# bash tests/e2e/run.sh # local: download via install.sh +# +# Optional env: +# PVM_VERSION_MAJOR_MINOR major.minor to test (default: 8.5) +# PVM_E2E_ONLY space-separated case file names to run (e.g. "01_install.sh 02_ls.sh") +set -euo pipefail + +HERE="$(cd "$(dirname "$0")" && pwd)" +source "$HERE/_lib.sh" + +for tool in curl tar nc expect cgi-fcgi pgrep; do + command -v "$tool" >/dev/null 2>&1 \ + || fail "missing required tool: $tool (install: expect libfcgi-bin)" +done + +# --------------------------------------------------------------------------- +# Stage pvm binary +# --------------------------------------------------------------------------- +if [[ -n "${PVM_BIN:-}" ]]; then + [[ -x "$PVM_BIN" ]] || fail "PVM_BIN set but not executable: $PVM_BIN" + : "${PVM_DIR:=$HOME/.local/share/pvm}" + mkdir -p "$PVM_DIR/bin" + cp "$PVM_BIN" "$PVM_DIR/bin/pvm" + PVM_BIN="$PVM_DIR/bin/pvm" + ok "using pre-built pvm at $PVM_BIN" +else + curl -fsSL https://raw.githubusercontent.com/WebProject-xyz/php-version-manager/main/install.sh | bash + PVM_BIN="$HOME/.local/share/pvm/bin/pvm" + ok "installed via install.sh" +fi +"$PVM_BIN" --version + +# --------------------------------------------------------------------------- +# Resolve target versions from upstream index +# --------------------------------------------------------------------------- +ARCH="$(uname -m)" +case "$ARCH" in + x86_64|amd64) TGT="linux-x86_64" ;; + aarch64|arm64) TGT="linux-aarch64" ;; + *) fail "unsupported arch $ARCH" ;; +esac + +VFILTER="${PVM_VERSION_MAJOR_MINOR:-8.5}" +ESCAPED_VFILTER="${VFILTER//./\\.}" + +INDEX_JSON=$(curl -fsSL "https://dl.static-php.dev/static-php-cli/bulk/?format=json") +ALL_VERS=$(echo "$INDEX_JSON" \ + | grep -oE "\"php-${ESCAPED_VFILTER}\\.[0-9]+-cli-${TGT}\\.tar\\.gz\"" \ + | sed -E "s|\"php-(${ESCAPED_VFILTER}\\.[0-9]+)-cli-${TGT}\\.tar\\.gz\"|\\1|" \ + | sort -V -u) +LATEST=$(echo "$ALL_VERS" | tail -n 1) +PREVIOUS=$(echo "$ALL_VERS" | tail -n 2 | head -n 1) +[[ -n "$LATEST" ]] || fail "no ${VFILTER}.x versions found upstream" +[[ -n "$PREVIOUS" && "$PREVIOUS" != "$LATEST" ]] || PREVIOUS="$LATEST" + +VDIR="$HOME/.local/share/pvm/versions/$PREVIOUS/bin" +MISSING_VER="${VFILTER}.99999" + +ok "latest ${VFILTER}.x = $LATEST / previous = $PREVIOUS" + +# --------------------------------------------------------------------------- +# Shared state dir + fpm config paths (used across cases) +# --------------------------------------------------------------------------- +E2E_STATE=$(mktemp -d) +FPM_PID_FILE=/tmp/php-fpm.pid +FPM_LOG_FILE=/tmp/php-fpm.log +FPM_SOCK=/tmp/php-fpm-www.sock + +cleanup() { + if [[ -f "$E2E_STATE/fpm.pid" ]]; then + kill -QUIT "$(cat "$E2E_STATE/fpm.pid")" 2>/dev/null || true + fi + rm -rf "$E2E_STATE" + rm -f "$FPM_SOCK" "$FPM_PID_FILE" "$FPM_LOG_FILE" +} +trap cleanup EXIT + +export PVM_BIN VFILTER LATEST PREVIOUS VDIR MISSING_VER E2E_STATE +export FPM_PID_FILE FPM_LOG_FILE FPM_SOCK + +# --------------------------------------------------------------------------- +# Run each case as a fresh bash subprocess +# --------------------------------------------------------------------------- +CASES=() +if [[ -n "${PVM_E2E_ONLY:-}" ]]; then + for c in $PVM_E2E_ONLY; do + CASES+=("$HERE/cases/$c") + done +else + while IFS= read -r f; do + CASES+=("$f") + done < <(find "$HERE/cases" -maxdepth 1 -name '[0-9][0-9]_*.sh' | sort) +fi + +PASSED=0 +FAILED=0 +for case_file in "${CASES[@]}"; do + case_name=$(basename "$case_file" .sh) + echo + echo -e "${BLUE}── ${case_name} ──${NC}" + if bash "$case_file"; then + PASSED=$((PASSED + 1)) + else + FAILED=$((FAILED + 1)) + echo -e "${RED}✗ ${case_name} failed${NC}" >&2 + # Stop on first failure — later cases depend on earlier state (fpm, install). + echo + echo -e "${RED}aborting: $FAILED failed, $PASSED passed before failure${NC}" >&2 + exit 1 + fi +done + +echo +echo -e "${GREEN}All $PASSED e2e cases passed on Linux.${NC}" +echo "Tested version: $PREVIOUS (latest upstream: $LATEST)" +echo "Pools: TCP 127.0.0.1:9000 + unix $FPM_SOCK" From a531a9614d4b8aa91f70fb7ca980767655c10684 Mon Sep 17 00:00:00 2001 From: Benjamin Fahl Date: Fri, 8 May 2026 22:31:59 +0200 Subject: [PATCH 4/5] test(e2e): sandbox guard, sandbox image, README, and CodeRabbit fixups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address CodeRabbit review feedback on PR #26 and harden the suite for local use. Sandboxing: * Add e2e_require_sandbox helper in _lib.sh that refuses to run unless /.dockerenv exists, GITHUB_ACTIONS=true, /proc/1/cgroup looks like a container, or PVM_E2E_FORCE=1 is set. Driver calls it first, so a curious developer cannot accidentally clobber their real $HOME/.local/share/pvm by running tests/e2e/run.sh on the host. * Ship tests/e2e/Dockerfile (ubuntu:24.04 + curl + expect + libfcgi-bin + netcat-openbsd) so contributors can `docker build -t pvm-e2e tests/e2e` and reproduce the CI sandbox locally. * Add tests/e2e/README.md walking through layout, the safety check, the local docker workflow, and the env-var overrides. Review fixups (PR #26): * README.md: add `text` language tag to the path-only fenced block (markdownlint MD040). * _lib.sh fcgi_call: trap RETURN to clean the temp dir even when cgi-fcgi exits non-zero under the caller's set -e. * run.sh: add --connect-timeout/--max-time on both curl invocations so a slow upstream cannot hang CI indefinitely. * run.sh + cases 09/10/12: parameterize the FPM TCP address via FPM_TCP_ADDR (default 127.0.0.1:9000) so a port collision is fixable by env, not a script edit. * cases 02/09/15: replace the SC2015 `A && ok || warn|fail` pattern with explicit if/else so the secondary branch only fires when the condition is false, not when ok itself happens to exit non-zero. * cases 07 + 15: read the pvm state dir from ${PVM_DIR:-…} instead of hardcoding $HOME/.local/share/pvm, so a user-customised PVM_DIR is honoured. --- README.md | 2 +- tests/e2e/Dockerfile | 28 +++++++ tests/e2e/README.md | 99 +++++++++++++++++++++++++ tests/e2e/_lib.sh | 26 ++++++- tests/e2e/cases/02_ls.sh | 8 +- tests/e2e/cases/07_patch_update.sh | 2 +- tests/e2e/cases/08_fpm_config.sh | 3 +- tests/e2e/cases/09_fpm_run.sh | 18 +++-- tests/e2e/cases/10_fcgi_tcp.sh | 4 +- tests/e2e/cases/12_php_ini_effective.sh | 2 +- tests/e2e/cases/15_uninstall.sh | 8 +- tests/e2e/run.sh | 13 +++- 12 files changed, 191 insertions(+), 22 deletions(-) create mode 100644 tests/e2e/Dockerfile create mode 100644 tests/e2e/README.md diff --git a/README.md b/README.md index 5cfec9c..ae974ff 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ After `pvm use `, every selected binary is on `$PATH` (CLI as `php`, FP PVM downloads a static `php-fpm` binary alongside `php` when you tick the `fpm` package during `pvm install`. The static-php-cli tarball ships only the binary — no `php-fpm.conf`, no pool files, no init script — so you wire those up yourself. The binary lives next to the CLI at: -``` +```text $PVM_DIR/versions//bin/php-fpm ``` diff --git a/tests/e2e/Dockerfile b/tests/e2e/Dockerfile new file mode 100644 index 0000000..98f3bde --- /dev/null +++ b/tests/e2e/Dockerfile @@ -0,0 +1,28 @@ +# Sandbox image for the pvm e2e suite. +# Build: docker build -t pvm-e2e tests/e2e +# Run: docker run --rm \ +# -v "$(pwd)/tests/e2e:/home/tester/e2e:ro" \ +# -v "$(pwd)/target/release/pvm:/home/tester/pvm:ro" \ +# -e PVM_BIN=/home/tester/pvm \ +# pvm-e2e bash /home/tester/e2e/run.sh +FROM ubuntu:24.04 + +ENV DEBIAN_FRONTEND=noninteractive + +# Runtime deps mirror what release.yml installs: +# curl, ca-certificates — for install.sh fallback + upstream tarball download +# bash — driver/case scripts +# netcat-openbsd — listener readiness check +# procps — pgrep for worker counting +# expect — drives interactive dialoguer prompts +# libfcgi-bin — supplies cgi-fcgi for FastCGI roundtrip cases +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl ca-certificates bash netcat-openbsd procps \ + expect libfcgi-bin \ + && rm -rf /var/lib/apt/lists/* + +RUN useradd -m -s /bin/bash tester +USER tester +WORKDIR /home/tester +ENV SHELL=/bin/bash +CMD ["/bin/bash"] diff --git a/tests/e2e/README.md b/tests/e2e/README.md new file mode 100644 index 0000000..62b4fd9 --- /dev/null +++ b/tests/e2e/README.md @@ -0,0 +1,99 @@ +# pvm e2e tests (Linux) + +Locks the README "Running PHP-FPM" walkthrough and the core `pvm` flows in lockstep with the upstream static-php-cli FPM tarballs. Runs on every PR via `release.yml` job **e2e tests - linux**. + +## Layout + +```text +tests/e2e/ +├── Dockerfile sandbox image (ubuntu:24.04 + expect + libfcgi-bin) +├── run.sh driver — version resolution + per-case execution +├── _lib.sh shared helpers (run_under_expect, fcgi_call, sandbox guard) +├── README.md this file +└── cases/ + ├── 01_install.sh real `pvm install` with interactive MultiSelect + ├── 02_ls.sh `pvm ls` discovers the version + package tags + ├── 03_use_wrapper.sh `pvm use` via shell wrapper switches PATH + ├── 04_current.sh `pvm current` + ├── 05_php_version_hook.sh `.php-version` cd-hook + ├── 06_use_missing.sh missing-version install prompt + decline (#24) + ├── 07_patch_update.sh patch-update detection + ├── 08_fpm_config.sh README php-fpm.conf + pool.d/{www,sock}.conf, `-t` + ├── 09_fpm_run.sh `php-fpm -F` listens on TCP + unix socket + ├── 10_fcgi_tcp.sh FastCGI roundtrip over TCP + ├── 11_fcgi_sock.sh FastCGI roundtrip over unix socket + ├── 12_php_ini_effective.sh `-c php.ini` is effective inside the worker + ├── 13_pid_log.sh pid file + error log written + ├── 14_fpm_shutdown.sh SIGQUIT clean shutdown + └── 15_uninstall.sh `pvm uninstall` removes the version dir +``` + +The driver runs each `cases/NN_*.sh` as a fresh bash subprocess so state from one case cannot mask bugs in the next. (The previous monolith silently passed because pvm's 24h `.update_check_guard` suppressed the patch-update prompt for any case after the first one to use it.) + +## Why the safety check + +`run.sh` mutates `$HOME/.local/share/pvm`, `/tmp/php-fpm.*`, and `~/.config/php-fpm`. Running on a dev machine would clobber whatever real pvm install lives there. So the driver refuses to run unless one of these is true: + +- `/.dockerenv` exists (you're inside a container) +- `GITHUB_ACTIONS=true` (you're on a hosted runner) +- `/proc/1/cgroup` shows a container runtime (docker, containerd, kubepods) +- `PVM_E2E_FORCE=1` is set (manual override at your own risk) + +## Running locally + +Build the sandbox image once (rebuild only when `Dockerfile` changes): + +```bash +docker build -t pvm-e2e tests/e2e +``` + +Build pvm from source: + +```bash +cargo build --release +``` + +Run the full suite: + +```bash +docker run --rm \ + -v "$(pwd)/tests/e2e:/home/tester/e2e:ro" \ + -v "$(pwd)/target/release/pvm:/home/tester/pvm:ro" \ + -e PVM_BIN=/home/tester/pvm \ + pvm-e2e bash /home/tester/e2e/run.sh +``` + +## Useful overrides + +| Env var | Default | Effect | +|---------|---------|--------| +| `PVM_BIN` | _unset_ | Path to a pre-built pvm. Unset → driver runs `install.sh` and pulls the latest GitHub release. | +| `PVM_VERSION_MAJOR_MINOR` | `8.5` | Major.minor line to test. Both `LATEST` and `PREVIOUS` patches must exist upstream. | +| `PVM_E2E_ONLY` | _unset_ | Space-separated case files to run, e.g. `"01_install.sh 07_patch_update.sh"`. Useful for reproducing one failure. | +| `FPM_TCP_ADDR` | `127.0.0.1:9000` | Override the TCP listener if `:9000` is busy. | +| `PVM_E2E_FORCE` | _unset_ | Set to `1` to bypass the sandbox guard. Don't. | + +### Run a single case + +```bash +docker run --rm \ + -v "$(pwd)/tests/e2e:/home/tester/e2e:ro" \ + -v "$(pwd)/target/release/pvm:/home/tester/pvm:ro" \ + -e PVM_BIN=/home/tester/pvm \ + -e PVM_E2E_ONLY="07_patch_update.sh" \ + pvm-e2e bash /home/tester/e2e/run.sh +``` + +Note: cases 09–13 depend on case 08 having written the FPM config and on a running FPM process — running them in isolation will fail unless you also include the cases that set up that state. + +### Test PHP 8.4 instead of 8.5 + +```bash +docker run --rm ... \ + -e PVM_VERSION_MAJOR_MINOR=8.4 \ + pvm-e2e bash /home/tester/e2e/run.sh +``` + +## In CI + +The job lives in `.github/workflows/release.yml` as `e2e tests - linux`, chained `tests` → `e2e tests - linux` → `release`. It builds pvm from source, installs `expect` + `libfcgi-bin` + `netcat-openbsd`, and invokes `tests/e2e/run.sh` directly on the runner — no Docker layer needed because the runner *is* the sandbox (`GITHUB_ACTIONS=true`). diff --git a/tests/e2e/_lib.sh b/tests/e2e/_lib.sh index c36e3a3..73a3f61 100755 --- a/tests/e2e/_lib.sh +++ b/tests/e2e/_lib.sh @@ -33,11 +33,35 @@ fcgi_call() { local php_body="$2" local script_dir script_dir=$(mktemp -d) + # RETURN trap fires even when set -e in the caller would otherwise abort + # mid-call due to a non-zero cgi-fcgi exit, so the temp dir is always removed. + trap 'rm -rf "$script_dir"' RETURN echo " "$script_dir/run.php" SCRIPT_FILENAME="$script_dir/run.php" \ SCRIPT_NAME=/run.php \ REQUEST_METHOD=GET \ QUERY_STRING="" \ cgi-fcgi -bind -connect "$connect" - rm -rf "$script_dir" +} + +# Safety: refuse to mutate the user's local pvm state outside Docker or GitHub Actions. +# Both run.sh and any case sourcing this lib hit $HOME/.local/share/pvm, /tmp, and +# $HOME/.config/php-fpm — running on a dev machine would clobber the user's setup. +e2e_require_sandbox() { + if [[ "${PVM_E2E_FORCE:-}" == "1" ]]; then + warn "PVM_E2E_FORCE=1 — running outside Docker / GitHub Actions on caller's request" + return 0 + fi + if [[ -f /.dockerenv ]]; then + return 0 + fi + if [[ "${GITHUB_ACTIONS:-}" == "true" ]]; then + return 0 + fi + if grep -qE '(/docker/|containerd|kubepods)' /proc/1/cgroup 2>/dev/null; then + return 0 + fi + fail "tests/e2e mutates \$HOME/.local/share/pvm, /tmp/php-fpm.*, and ~/.config/php-fpm. + Run inside Docker (see tests/e2e/README.md) or GitHub Actions. + Override at your own risk with PVM_E2E_FORCE=1." } diff --git a/tests/e2e/cases/02_ls.sh b/tests/e2e/cases/02_ls.sh index 57696d2..e204bfb 100755 --- a/tests/e2e/cases/02_ls.sh +++ b/tests/e2e/cases/02_ls.sh @@ -6,6 +6,8 @@ source "$(dirname "$0")/../_lib.sh" step "pvm ls — version discovery + package tags" "$PVM_BIN" ls "$PVM_BIN" ls | grep -q "$PREVIOUS" || fail "pvm ls missing $PREVIOUS" -"$PVM_BIN" ls | grep -E "$PREVIOUS.*cli.*fpm" >/dev/null \ - && ok "pvm ls shows [cli, fpm] for $PREVIOUS" \ - || warn "pvm ls did not show both [cli, fpm] tags" +if "$PVM_BIN" ls | grep -qE "$PREVIOUS.*cli.*fpm"; then + ok "pvm ls shows [cli, fpm] for $PREVIOUS" +else + warn "pvm ls did not show both [cli, fpm] tags" +fi diff --git a/tests/e2e/cases/07_patch_update.sh b/tests/e2e/cases/07_patch_update.sh index e98f6b8..d4f50ac 100755 --- a/tests/e2e/cases/07_patch_update.sh +++ b/tests/e2e/cases/07_patch_update.sh @@ -10,7 +10,7 @@ fi # pvm rate-limits the patch-update check to once per 24h via this guard file; # clear it so the prompt actually fires when this case runs after another `pvm use`. -rm -f "$HOME/.local/share/pvm/.update_check_guard" +rm -f "${PVM_DIR:-$HOME/.local/share/pvm}/.update_check_guard" step "pvm use $VFILTER offers $LATEST over $PREVIOUS" diff --git a/tests/e2e/cases/08_fpm_config.sh b/tests/e2e/cases/08_fpm_config.sh index 0b8636f..5d037bc 100755 --- a/tests/e2e/cases/08_fpm_config.sh +++ b/tests/e2e/cases/08_fpm_config.sh @@ -6,6 +6,7 @@ source "$(dirname "$0")/../_lib.sh" step "Write README php-fpm config and validate via -t" USER_NAME="$(whoami)" +FPM_TCP_ADDR="${FPM_TCP_ADDR:-127.0.0.1:9000}" mkdir -p "$HOME/.config/php-fpm/pool.d" cat > "$HOME/.config/php-fpm/php-fpm.conf" < "$HOME/.config/php-fpm/pool.d/www.conf" < "$E2E_STATE/fpm.pid" +TCP_HOST="${FPM_TCP_ADDR%:*}" +TCP_PORT="${FPM_TCP_ADDR##*:}" + for i in $(seq 1 60); do - if nc -z 127.0.0.1 9000 2>/dev/null && [[ -S "$FPM_SOCK" ]]; then - ok "fpm listening on TCP :9000 + unix $FPM_SOCK (after ${i}*100ms)" + if nc -z "$TCP_HOST" "$TCP_PORT" 2>/dev/null && [[ -S "$FPM_SOCK" ]]; then + ok "fpm listening on TCP $FPM_TCP_ADDR + unix $FPM_SOCK (after ${i}*100ms)" break fi sleep 0.1 done -if ! nc -z 127.0.0.1 9000 2>/dev/null; then +if ! nc -z "$TCP_HOST" "$TCP_PORT" 2>/dev/null; then echo "--- stdout ---"; cat /tmp/fpm.stdout || true echo "--- stderr ---"; cat /tmp/fpm.stderr || true - fail "TCP :9000 not listening" + fail "TCP $FPM_TCP_ADDR not listening" fi [[ -S "$FPM_SOCK" ]] || fail "unix socket $FPM_SOCK not created" WORKER_COUNT=$(pgrep -P "$FPM_PID" 2>/dev/null | wc -l || echo 0) -[[ "$WORKER_COUNT" -ge 2 ]] && ok "$WORKER_COUNT workers spawned" \ - || warn "only $WORKER_COUNT workers detected" +if [[ "$WORKER_COUNT" -ge 2 ]]; then + ok "$WORKER_COUNT workers spawned" +else + warn "only $WORKER_COUNT workers detected" +fi diff --git a/tests/e2e/cases/10_fcgi_tcp.sh b/tests/e2e/cases/10_fcgi_tcp.sh index e48a900..155bf39 100755 --- a/tests/e2e/cases/10_fcgi_tcp.sh +++ b/tests/e2e/cases/10_fcgi_tcp.sh @@ -3,8 +3,8 @@ set -euo pipefail source "$(dirname "$0")/../_lib.sh" -step "FastCGI roundtrip over TCP 127.0.0.1:9000" -RESPONSE=$(fcgi_call "127.0.0.1:9000" 'echo "FCGI_OK\n";' 2>&1) +step "FastCGI roundtrip over TCP $FPM_TCP_ADDR" +RESPONSE=$(fcgi_call "$FPM_TCP_ADDR" 'echo "FCGI_OK\n";' 2>&1) echo "$RESPONSE" echo "$RESPONSE" | grep -q "FCGI_OK" || fail "TCP roundtrip did not return FCGI_OK" ok "TCP FastCGI executed PHP" diff --git a/tests/e2e/cases/12_php_ini_effective.sh b/tests/e2e/cases/12_php_ini_effective.sh index 5138df9..d92daf8 100755 --- a/tests/e2e/cases/12_php_ini_effective.sh +++ b/tests/e2e/cases/12_php_ini_effective.sh @@ -4,7 +4,7 @@ set -euo pipefail source "$(dirname "$0")/../_lib.sh" step "-c php.ini effective inside worker (memory_limit, expose_php)" -RESPONSE=$(fcgi_call "127.0.0.1:9000" \ +RESPONSE=$(fcgi_call "$FPM_TCP_ADDR" \ 'echo "MEM=" . ini_get("memory_limit") . "\n"; echo "EXPOSE=" . (ini_get("expose_php") ? "On" : "Off") . "\n";' \ 2>&1) echo "$RESPONSE" diff --git a/tests/e2e/cases/15_uninstall.sh b/tests/e2e/cases/15_uninstall.sh index cb08b69..f66d90a 100755 --- a/tests/e2e/cases/15_uninstall.sh +++ b/tests/e2e/cases/15_uninstall.sh @@ -8,9 +8,11 @@ step "pvm uninstall $PREVIOUS" "$PVM_BIN" uninstall "$PREVIOUS" -[[ ! -d "$HOME/.local/share/pvm/versions/$PREVIOUS" ]] \ - && ok "versions/$PREVIOUS directory removed" \ - || fail "uninstall left versions/$PREVIOUS in place" +if [[ ! -d "${PVM_DIR:-$HOME/.local/share/pvm}/versions/$PREVIOUS" ]]; then + ok "versions/$PREVIOUS directory removed" +else + fail "uninstall left versions/$PREVIOUS in place" +fi if "$PVM_BIN" ls 2>&1 | grep -q "$PREVIOUS"; then fail "pvm ls still shows $PREVIOUS after uninstall" diff --git a/tests/e2e/run.sh b/tests/e2e/run.sh index bef0545..544a912 100755 --- a/tests/e2e/run.sh +++ b/tests/e2e/run.sh @@ -14,6 +14,10 @@ set -euo pipefail HERE="$(cd "$(dirname "$0")" && pwd)" source "$HERE/_lib.sh" +# Refuse to run on a dev machine — we mutate $HOME/.local/share/pvm and /tmp. +# Override with PVM_E2E_FORCE=1 if you really mean it. +e2e_require_sandbox + for tool in curl tar nc expect cgi-fcgi pgrep; do command -v "$tool" >/dev/null 2>&1 \ || fail "missing required tool: $tool (install: expect libfcgi-bin)" @@ -30,7 +34,8 @@ if [[ -n "${PVM_BIN:-}" ]]; then PVM_BIN="$PVM_DIR/bin/pvm" ok "using pre-built pvm at $PVM_BIN" else - curl -fsSL https://raw.githubusercontent.com/WebProject-xyz/php-version-manager/main/install.sh | bash + curl -fsSL --connect-timeout 10 --max-time 60 \ + https://raw.githubusercontent.com/WebProject-xyz/php-version-manager/main/install.sh | bash PVM_BIN="$HOME/.local/share/pvm/bin/pvm" ok "installed via install.sh" fi @@ -49,7 +54,8 @@ esac VFILTER="${PVM_VERSION_MAJOR_MINOR:-8.5}" ESCAPED_VFILTER="${VFILTER//./\\.}" -INDEX_JSON=$(curl -fsSL "https://dl.static-php.dev/static-php-cli/bulk/?format=json") +INDEX_JSON=$(curl -fsSL --connect-timeout 10 --max-time 30 \ + "https://dl.static-php.dev/static-php-cli/bulk/?format=json") ALL_VERS=$(echo "$INDEX_JSON" \ | grep -oE "\"php-${ESCAPED_VFILTER}\\.[0-9]+-cli-${TGT}\\.tar\\.gz\"" \ | sed -E "s|\"php-(${ESCAPED_VFILTER}\\.[0-9]+)-cli-${TGT}\\.tar\\.gz\"|\\1|" \ @@ -71,6 +77,7 @@ E2E_STATE=$(mktemp -d) FPM_PID_FILE=/tmp/php-fpm.pid FPM_LOG_FILE=/tmp/php-fpm.log FPM_SOCK=/tmp/php-fpm-www.sock +FPM_TCP_ADDR="${FPM_TCP_ADDR:-127.0.0.1:9000}" cleanup() { if [[ -f "$E2E_STATE/fpm.pid" ]]; then @@ -82,7 +89,7 @@ cleanup() { trap cleanup EXIT export PVM_BIN VFILTER LATEST PREVIOUS VDIR MISSING_VER E2E_STATE -export FPM_PID_FILE FPM_LOG_FILE FPM_SOCK +export FPM_PID_FILE FPM_LOG_FILE FPM_SOCK FPM_TCP_ADDR # --------------------------------------------------------------------------- # Run each case as a fresh bash subprocess From a7ec854695150632c7fd8aff9eaf9ad690b9e168 Mon Sep 17 00:00:00 2001 From: Benjamin Fahl Date: Tue, 12 May 2026 18:39:41 +0200 Subject: [PATCH 5/5] test(e2e): fix SC2015 in 08_fpm_config and pgrep|wc fallback in 09_fpm_run - 08_fpm_config.sh: replace 'A && ok || fail' with if/then/else (SC2015). Same pattern was already cleaned in 02_ls.sh / 15_uninstall.sh; this one was missed. - 09_fpm_run.sh: pgrep | wc -l || echo 0 produced a two-line value under pipefail when pgrep had no matches. Switch to '|| true' so WORKER_COUNT holds only wc's line count. --- tests/e2e/cases/08_fpm_config.sh | 8 +++++--- tests/e2e/cases/09_fpm_run.sh | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/e2e/cases/08_fpm_config.sh b/tests/e2e/cases/08_fpm_config.sh index 5d037bc..eab7fbd 100755 --- a/tests/e2e/cases/08_fpm_config.sh +++ b/tests/e2e/cases/08_fpm_config.sh @@ -56,9 +56,11 @@ EOF ok "wrote php-fpm.conf, pool.d/{www,sock}.conf, php.ini" -"$VDIR/php-fpm" -y "$HOME/.config/php-fpm/php-fpm.conf" -t \ - && ok "php-fpm -t OK" \ - || fail "php-fpm -t failed" +if "$VDIR/php-fpm" -y "$HOME/.config/php-fpm/php-fpm.conf" -t; then + ok "php-fpm -t OK" +else + fail "php-fpm -t failed" +fi "$VDIR/php-fpm" -v MOD_COUNT=$("$VDIR/php-fpm" -m | grep -cE '^[a-z]' || true) diff --git a/tests/e2e/cases/09_fpm_run.sh b/tests/e2e/cases/09_fpm_run.sh index eeb0216..d0bb825 100755 --- a/tests/e2e/cases/09_fpm_run.sh +++ b/tests/e2e/cases/09_fpm_run.sh @@ -35,7 +35,7 @@ if ! nc -z "$TCP_HOST" "$TCP_PORT" 2>/dev/null; then fi [[ -S "$FPM_SOCK" ]] || fail "unix socket $FPM_SOCK not created" -WORKER_COUNT=$(pgrep -P "$FPM_PID" 2>/dev/null | wc -l || echo 0) +WORKER_COUNT=$(pgrep -P "$FPM_PID" 2>/dev/null | wc -l || true) if [[ "$WORKER_COUNT" -ge 2 ]]; then ok "$WORKER_COUNT workers spawned" else