Files
cloud-apache-container/scripts/tests/lsphp-abi-gate.test.sh
T

99 lines
4.5 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
## lsphp-abi-gate.test.sh — proves scripts/lsphp-abi-gate.sh can FAIL.
##
## The gate is what lets the emergency php.net lsphp build keep LiteSpeed's
## packaged PECL .so files. A gate that always says "compatible" would be worse
## than none, so every rule is exercised against synthetic header trees whose
## outcome is known: three layouts that must pass (unchanged; new member in
## padding; member appended to a *_globals struct) and five that must fail
## (member inserted mid-struct; member removed; member retyped/resized; a
## non-globals struct grown at the end; a struct embedded in another growing).
## Plus: a probe that extracts too few members must refuse, not pass.
##
## Needs gcc + pahole (dwarves). Exits 0 only if every case behaves.
set -euo pipefail
HERE=$(cd "$(dirname "$0")/.." && pwd)
GATE="$HERE/lsphp-abi-gate.sh"
for t in gcc pahole; do
command -v "$t" >/dev/null 2>&1 || { echo "SKIP-FAIL: $t not installed (apt-get install gcc dwarves)" >&2; exit 1; }
done
T=$(mktemp -d)
trap 'rm -rf "$T"' EXIT
## $1 = dir, $2 = body of struct _zend_object, $3 = body of _zend_executor_globals,
## $4 = extra declarations
mk() {
mkdir -p "$1/php/main"
cat >"$1/php/main/php.h" <<EOF
#include <stdint.h>
#include <stdbool.h>
struct _zend_object { $2 };
struct _zend_executor_globals { $3 };
struct _holder { struct _zend_object obj; int after; };
typedef struct { int a; void *b; } anon_cache;
$4
struct _zend_executor_globals executor_globals;
struct _holder holder_sym;
anon_cache anon_sym;
EOF
}
BASE_OBJ='uint32_t gc; uint32_t handle; bool flag; void *ce; void *handlers;'
BASE_EG='void *a; int b; void *reserved[4];'
mk "$T/old" "$BASE_OBJ" "$BASE_EG" ''
PASS=0; FAILN=0
expect() { # $1 = pass|fail, $2 = name, $3 = new dir
local rc=0
ABI_MIN_MEMBERS=5 bash "$GATE" "$T/old" "$3" "$T/w-$2" >"$T/out-$2" 2>&1 || rc=$?
if { [ "$1" = pass ] && [ "$rc" = 0 ]; } || { [ "$1" = fail ] && [ "$rc" != 0 ]; }; then
PASS=$((PASS + 1)); echo "ok - $2 ($1, rc=$rc)"
else
FAILN=$((FAILN + 1)); echo "FAIL - $2: expected $1, got rc=$rc"; sed 's/^/ /' "$T/out-$2"
fi
}
mk "$T/same" "$BASE_OBJ" "$BASE_EG" ''
expect pass unchanged "$T/same"
## bool after `flag` lands in the 3 padding bytes before the pointer `ce`
mk "$T/pad" 'uint32_t gc; uint32_t handle; bool flag; bool newflag; void *ce; void *handlers;' "$BASE_EG" ''
expect pass new-member-in-padding "$T/pad"
mk "$T/eg" "$BASE_OBJ" 'void *a; int b; void *reserved[4]; void *delayed_oom;' ''
expect pass globals-appended "$T/eg"
mk "$T/mid" 'uint32_t gc; void *inserted; uint32_t handle; bool flag; void *ce; void *handlers;' "$BASE_EG" ''
expect fail member-inserted-mid-struct "$T/mid"
mk "$T/rm" 'uint32_t gc; uint32_t handle; bool flag; void *handlers;' "$BASE_EG" ''
expect fail member-removed "$T/rm"
mk "$T/retype" 'uint32_t gc; uint64_t handle; bool flag; void *ce; void *handlers;' "$BASE_EG" ''
expect fail member-resized "$T/retype"
## appended to a NON-globals struct: no old member moves, but sizeof grows —
## extensions that allocate sizeof(zend_object)+x would under-allocate
mk "$T/grow" 'uint32_t gc; uint32_t handle; bool flag; void *ce; void *handlers; void *extra;' "$BASE_EG" ''
expect fail non-globals-struct-grew "$T/grow"
## same change seen from the embedding struct: the report must name _holder.after
## as MOVED, i.e. the container check works on its own, not only via the size rule
if grep -E '^BAD moved member _holder int_after#1' "$T/out-non-globals-struct-grew" >/dev/null; then
PASS=$((PASS + 1)); echo "ok - embedded-struct-shifts-container (reported _holder.after moved)"
else
FAILN=$((FAILN + 1)); echo "FAIL - embedded-struct-shifts-container: _holder.after not reported"; sed 's/^/ /' "$T/out-non-globals-struct-grew"
fi
## the anonymous typedef is tracked too
mkdir -p "$T/anon/php/main"
sed 's/typedef struct { int a; void \*b; } anon_cache;/typedef struct { int a; int z; void *b; long c; } anon_cache;/' "$T/old/php/main/php.h" >"$T/anon/php/main/php.h"
expect fail anonymous-typedef-changed "$T/anon"
## a probe that finds (almost) nothing must refuse
rc=0; ABI_MIN_MEMBERS=1000 bash "$GATE" "$T/old" "$T/same" "$T/w-min" >"$T/out-min" 2>&1 || rc=$?
if [ "$rc" != 0 ] && grep 'probe is broken' "$T/out-min" >/dev/null; then PASS=$((PASS + 1)); echo "ok - too-few-members-refused (rc=$rc)"
else FAILN=$((FAILN + 1)); echo "FAIL - too-few-members-refused: rc=$rc"; cat "$T/out-min"; fi
echo "lsphp-abi-gate: $PASS passed, $FAILN failed"
[ "$FAILN" = 0 ]