Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
persist-credentials: false

- name: ShellCheck
run: shellcheck -x git-extensions.sh git-release-api.sh git-release-style.sh git-release-langpack.sh lib/github-release.sh tests/test-release-scripts.sh tests/test-release-api.sh phpbb-test-board/setup-board.sh phpbb-test-board/run-unit-tests.sh tests/test-phpbb-test-board.sh
run: shellcheck -x git-extensions.sh git-release-api.sh git-release-style.sh git-release-langpack.sh lib/github-release.sh tests/test-release-scripts.sh tests/test-release-api.sh phpbb-test-board/setup-board.sh phpbb-test-board/run-unit-tests.sh phpbb-test-board/php-lint.sh tests/test-phpbb-test-board.sh

- name: Tests
run: tests/test-release-scripts.sh
Expand Down
11 changes: 11 additions & 0 deletions phpbb-test-board/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ They build a board on SQLite and run an extension on it with PHP error logging o
- `git`, `php` (with the `sqlite3` extension), `composer`, `curl`, `jq` and `openssl`
- Python 3 with `venv`
- ImageMagick's `convert`, for the reassignthumbs feature check
- Docker, for `php-lint.sh`

The Python scripts create their own virtualenv (`.venv` in this folder) on first run and install `requirements.txt` into it; nothing needs installing or activating by hand. `screenshots.py` also downloads Playwright's Chromium on its first run.

Expand Down Expand Up @@ -57,6 +58,16 @@ Runs the extension's `phpunit.xml.dist` the way [phpbb-extensions/test-framework

Exit status: PHPUnit's, 1 for a setup failure, 2 for bad arguments. Needs `git`, `php`, `composer`, `rsync` and `jq`.

### `php-lint.sh`: syntax check on other PHP versions

```bash
phpbb-test-board/php-lint.sh path/to/extension [-p 7.4,8.4] [--strict]
```

Runs `php -l` on every PHP file under each PHP version (default 7.4 and 8.4), in a throwaway container from the official `php:VERSION-cli` image, so an extension can be checked against its minimum PHP without that PHP installed. The folder is mounted read-only, the container has no network and is removed afterwards; `vendor/`, `node_modules/` and `.git/` are skipped. Compile-time deprecation notices, such as implicitly nullable parameters on PHP 8.4, are shown, and fail the run only with `--strict`. It needs no board.

Exit status: 0 when every file passes on every version, 1 when a file fails or Docker fails, 2 for bad arguments. The first run of a version pulls its image.

### `smoke_test.py`: does the extension break anything?

```bash
Expand Down
153 changes: 153 additions & 0 deletions phpbb-test-board/php-lint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
#!/usr/bin/bash
# php-lint.sh
#
# Lints every PHP file of an extension (or any folder) with `php -l` under
# one or more PHP versions, each in a throwaway Docker container from the
# official php:VERSION-cli image. This checks an extension against its
# minimum PHP version (7.4 for phpbbmodders extensions) without that PHP
# being installed on this machine.
#
# The folder is mounted read-only, the container has no network and is
# removed when it exits. vendor/, node_modules/ and .git/ are skipped.
# Compile-time deprecation notices (for example implicitly nullable
# parameters on PHP 8.4) are shown, and fail the run only with --strict.
#
# Usage: php-lint.sh [-p VERSIONS] [-s] DIR
# Outputs: one line per PHP version with the file count and result, plus the
# message for every file that fails, on stdout
# Exit status: 0 when every file passes on every version, 1 when a file
# fails or Docker fails, 2 for bad arguments.

set -Eeuo pipefail

usage() {
cat <<'EOF'
Usage: php-lint.sh [options] DIR

Lint every PHP file in DIR with `php -l` under each chosen PHP version, in a
throwaway Docker container (official php:VERSION-cli image, DIR mounted
read-only, no network). vendor/, node_modules/ and .git/ are skipped.

Arguments:
DIR the extension's checkout, or any folder of PHP files

Options:
-p, --php VERSIONS comma-separated PHP versions (default: 7.4,8.4)
-s, --strict also fail on compile-time deprecation notices
-h, --help show this help and exit

Examples:
php-lint.sh ~/repos/contactadmin
php-lint.sh -p 7.4 ~/repos/contactadmin
php-lint.sh -p 7.4,8.2,8.4 --strict ~/repos/phpbb-ext-wiki
EOF
}

die() {
echo "Error: $*" >&2
exit 1
}

usage_error() {
echo "Error: $*" >&2
echo "Run with --help for usage." >&2
exit 2
}

versions="7.4,8.4"
strict=0
dir=""

if [ "$#" -eq 0 ]; then
usage
exit 0
fi

while [ "$#" -gt 0 ]; do
case "$1" in
-p|--php)
[ "$#" -ge 2 ] || usage_error "$1 needs a value"
versions="$2"
shift 2
;;
-s|--strict)
strict=1
shift
;;
-h|--help)
usage
exit 0
;;
-*)
usage_error "unknown option: $1"
;;
*)
[ -z "$dir" ] || usage_error "only one DIR may be given"
dir="$1"
shift
;;
esac
done

[ -n "$dir" ] || usage_error "DIR is required"
[ -d "$dir" ] || usage_error "not a directory: $dir"
dir="$(cd "$dir" && pwd)"

# Validate every version before starting a container: MAJOR.MINOR only, so
# the image tag can't be anything but an official php:X.Y-cli image.
IFS=',' read -r -a version_list <<<"$versions"
[ "${#version_list[@]}" -gt 0 ] || usage_error "--php needs at least one version"
for version in "${version_list[@]}"; do
[[ "$version" =~ ^[0-9]+\.[0-9]+$ ]] || usage_error "bad PHP version: '$version' (expected MAJOR.MINOR, e.g. 7.4)"
done

command -v docker >/dev/null 2>&1 || die "docker is required but not installed"
docker info >/dev/null 2>&1 || die "cannot reach the Docker daemon (is it running, and may this user use it?)"

# Runs inside the container: lints each file, prints "FAIL <file>" with the
# message for a parse error, "DEPRECATED <file>" with the notice for a
# compile-time deprecation, then a "TOTAL <files> <failed> <deprecated>" line.
# shellcheck disable=SC2016 # expanded by the container's shell, not this one
lint_script='
total=0; failed=0; deprecated=0
while IFS= read -r -d "" file; do
total=$((total + 1))
if ! out="$(php -d display_errors=stderr -d error_reporting=-1 -l "$file" 2>&1)"; then
failed=$((failed + 1))
printf "FAIL %s\n%s\n" "${file#/src/}" "$out"
elif printf "%s" "$out" | grep -q "Deprecated:"; then
deprecated=$((deprecated + 1))
printf "DEPRECATED %s\n%s\n" "${file#/src/}" "$(printf "%s" "$out" | grep "Deprecated:")"
fi
done < <(find /src \( -name vendor -o -name node_modules -o -name .git \) -prune -o -type f -name "*.php" -print0 | sort -z)
printf "TOTAL %s %s %s\n" "$total" "$failed" "$deprecated"
'

status=0
for version in "${version_list[@]}"; do
image="php:${version}-cli"
if ! output="$(docker run --rm --network none --read-only \
-v "$dir:/src:ro" "$image" bash -c "$lint_script" 2>&1)"; then
echo "$output" >&2
die "the PHP $version container failed (could the image $image not be pulled?)"
fi

summary="$(grep '^TOTAL ' <<<"$output" | tail -n 1)" || die "no result from the PHP $version container"
read -r _ total failed deprecated <<<"$summary"
grep -v '^TOTAL ' <<<"$output" || true

result="ok"
if [ "$failed" -gt 0 ]; then
result="FAIL ($failed with errors)"
status=1
elif [ "$deprecated" -gt 0 ]; then
result="ok, $deprecated with deprecation notices"
if [ "$strict" -eq 1 ]; then
result="FAIL ($deprecated with deprecation notices, --strict)"
status=1
fi
fi
echo "PHP $version: $total files, $result"
done

exit "$status"
13 changes: 12 additions & 1 deletion tests/test-phpbb-test-board.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/bash
# Option-handling tests for the phpbb-test-board tools: help output, exit
# status for bad arguments, and the error for a missing board. Does not build
# a board or contact phpBB, and run-unit-tests.sh is only given bad arguments;
# a board or contact phpBB, and run-unit-tests.sh and php-lint.sh are only given bad arguments;
# the Python scripts' first run creates their virtualenv
# (phpbb-test-board/.venv), which needs network access once.
# Usage: tests/test-phpbb-test-board.sh
Expand Down Expand Up @@ -142,6 +142,17 @@ check "run-unit-tests.sh with two directories exits 2" \
check "run-unit-tests.sh rejects a composer name that is not vendor/name" \
output_has "composer.json has no valid vendor/name" "$tools/run-unit-tests.sh" -e "$tmp/no-env" "$tmp/ext-bad-name"

# php-lint.sh: option handling only; every case exits before Docker is used
check "php-lint.sh without arguments shows help and exits 0" exits_with 0 "$tools/php-lint.sh"
check "php-lint.sh --help shows usage" output_has "Usage: php-lint.sh" "$tools/php-lint.sh" --help
check "php-lint.sh with an unknown option exits 2" exits_with 2 "$tools/php-lint.sh" --nope "$tmp"
check "php-lint.sh with a missing directory exits 2" exits_with 2 "$tools/php-lint.sh" "$tmp/no-such-dir"
check "php-lint.sh with two directories exits 2" exits_with 2 "$tools/php-lint.sh" "$tmp" "$tmp"
check "php-lint.sh with --php and no value exits 2" exits_with 2 "$tools/php-lint.sh" "$tmp" --php
check "php-lint.sh rejects a version that is not MAJOR.MINOR" \
output_has "bad PHP version: '7'" "$tools/php-lint.sh" -p 7 "$tmp"
check "php-lint.sh rejects an image-like version" exits_with 2 "$tools/php-lint.sh" -p "7.4-cli;id" "$tmp"

if [ "$failures" -eq 0 ]; then
echo "All tests passed."
else
Expand Down
Loading