Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
312b29b
test(config-command): add behat context with shared steps
mrrobot47 Oct 17, 2026
fc238eb
test(config-command): cover config help and usage
mrrobot47 Oct 17, 2026
cbf40eb
test(config-command): cover config get
mrrobot47 Oct 17, 2026
76cb66d
test(config-command): cover config set
mrrobot47 Oct 17, 2026
c41e999
test(config-command): cover config unset
mrrobot47 Oct 17, 2026
4ecd568
test(config-command): cover config list
mrrobot47 Oct 17, 2026
fc233db
test(config-command): cover EE_CONFIG_PATH, bootstrap defaults and ru…
mrrobot47 Oct 17, 2026
dc62ab7
test(config-command): cover the default global config path
mrrobot47 Oct 17, 2026
b0287e5
test(config-command): add coverage inventory
mrrobot47 Oct 17, 2026
6f5aabd
test(config-command): harden real config restore and fix context steps
mrrobot47 Oct 17, 2026
2f36ea3
test(config-command): correct quiet scenarios and extend coverage
mrrobot47 Oct 17, 2026
28f5f13
test(config-command): run the CFG-8 scenarios as regular ones
mrrobot47 Oct 17, 2026
53e6736
ci: run the behat suite on GitHub Actions
mrrobot47 Oct 19, 2026
9e79e67
ci: finalize the behat workflow
mrrobot47 Oct 19, 2026
f7191ca
ci: re-fetch docker-compose when the first run leaves a broken binary
mrrobot47 Oct 19, 2026
eb3759b
test(behat): leave the known-bug scenarios out until their fixes land
mrrobot47 Oct 19, 2026
f5165ab
test(config-command): harden the real-config snapshot and the JSON as…
mrrobot47 Oct 19, 2026
fd470ad
test(config-command): reapply the original mode when restoring the re…
mrrobot47 Oct 19, 2026
cd259ff
test(config-command): refuse chained config symlinks and keep real co…
mrrobot47 Oct 19, 2026
c2eb33d
test(config-command): reject short backup writes and omit command out…
mrrobot47 Oct 19, 2026
a7cbf81
test(config-command): stop the whole command tree before the signal-t…
mrrobot47 Oct 19, 2026
f10059d
docs(config-command): describe coverage gaps without workspace refere…
mrrobot47 Oct 20, 2026
bcff141
ci(config-command): run the behat lanes on every supported PHP version
mrrobot47 Oct 20, 2026
42e1f17
test(config-command): match lists by membership and compare whole mul…
mrrobot47 Oct 20, 2026
d416d84
test(config-command): drop the site cleanup for sites the suite never…
mrrobot47 Oct 23, 2026
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
181 changes: 181 additions & 0 deletions .github/workflows/behat.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# Behat suite of an EasyEngine package, run inside a fresh core checkout on a throwaway runner.
name: Behat

on:
pull_request:
push:
branches:
- develop
workflow_dispatch:

permissions:
contents: read

concurrency:
group: behat-${{ github.ref }}
cancel-in-progress: true

env:
CORE_REF: develop
COMPOSE_VERSION: v5.5.1
# Not RCLONE_*: rclone reads those variables as its own flags.
RCLONE_RELEASE: v1.75.1
# No public DNS (Let's Encrypt) or real backup storage on hosted runners.
EXCLUDE_TAGS: '~@le&&~@remote-real'
# Local rclone alias written to EE's rclone-path, for suites with @remote scenarios ('eetest:ee-backups'); empty = no rclone.
EE_RCLONE_PATH: ''
COMPOSE_INTERACTIVE_NO_CLI: 1
RCLONE_CONFIG: /etc/rclone/rclone.conf
EE_BEHAT_DESTRUCTIVE: 1

jobs:
behat:
name: Behat ${{ matrix.lane }} (PHP ${{ matrix.php }})
runs-on: ubuntu-latest
timeout-minutes: 150
strategy:
fail-fast: false
matrix:
php: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5']
lane: [main]
include:
- lane: main
tags: '~@known-bug'
steps:
- name: Check out the package
uses: actions/checkout@v7
with:
path: pkg

- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: none
tools: composer
extensions: pcntl, curl, sqlite3, zip, dom, mbstring, json

- name: Get Composer cache directory
id: composer-cache
run: echo "dir=$(composer config -g cache-files-dir)" >> "$GITHUB_OUTPUT"

- name: Set up Composer caching
uses: actions/cache@v6
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-php${{ matrix.php }}-${{ github.sha }}
restore-keys: ${{ runner.os }}-composer-php${{ matrix.php }}-

- name: Prepare the host
run: |
# The runner image ships web and database servers; EE needs 80/443.
sudo systemctl stop nginx apache2 mysql 2>/dev/null || true
sudo ss -ltnp | grep -E ':(80|443|3306) ' || true
sudo curl -fsSL "https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-linux-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker version --format 'docker {{.Server.Version}}'
docker-compose version
openssl version
echo "umask $(umask), under sudo $(sudo sh -c umask)"
# Suites that request the host's public IP use the first of these; EE picks a frontend subnet outside eth0's.
echo "hostname -I: $(hostname -I)"
ip -4 route
if [ -n "$EE_RCLONE_PATH" ]; then
curl -fsSL "https://github.com/rclone/rclone/releases/download/${RCLONE_RELEASE}/rclone-${RCLONE_RELEASE}-linux-amd64.zip" -o "$RUNNER_TEMP/rclone.zip"
unzip -q -j -o "$RUNNER_TEMP/rclone.zip" '*/rclone' -d "$RUNNER_TEMP"
sudo install -m 0755 "$RUNNER_TEMP/rclone" /usr/local/bin/rclone
sudo mkdir -p "$(dirname "$RCLONE_CONFIG")" /var/tmp/ee-rclone
printf '[%s]\ntype = alias\nremote = /var/tmp/ee-rclone\n' "${EE_RCLONE_PATH%%:*}" | sudo tee "$RCLONE_CONFIG" >/dev/null
sudo -E rclone listremotes
fi

- name: Set up EasyEngine core
run: |
git clone -q --depth 1 -b "$CORE_REF" https://github.com/EasyEngine/easyengine.git easyengine
cd easyengine
# All easyengine/* packages on dev-develop (as core CI does), except the one under test: that's this checkout.
php -r '
$pkg = json_decode(file_get_contents("../pkg/composer.json"), true)["name"];
$j = json_decode(file_get_contents("composer.json"), true);
foreach ($j["require"] as $k => $v) if (strpos($k, "easyengine/") === 0) $j["require"][$k] = "dev-develop";
$repos = isset($j["repositories"]) ? $j["repositories"] : [];
array_unshift($repos, ["type" => "path", "url" => "../pkg", "options" => ["symlink" => false, "versions" => [$pkg => "dev-develop"]]]);
$j["repositories"] = $repos;
file_put_contents("composer.json", json_encode($j, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n");
file_put_contents(getenv("GITHUB_ENV"), "PKG=$pkg\n", FILE_APPEND);'
composer update --prefer-dist --no-progress --no-interaction
composer show "$(jq -r .name ../pkg/composer.json)" | grep -E '^(versions|source|dist) ' || true
rm -rf features
cp -R ../pkg/features features
# Some commands (clone, sync) shell out to `ee` from PATH.
sudo ln -sf "$PWD/bin/ee" /usr/local/bin/ee

- name: Initialise EasyEngine
working-directory: easyengine
run: |
sudo -E bin/ee cli info
# The first non-cli command runs the initial setup (DB, config, image pulls).
sudo -E bin/ee site list || true
# Non-LE commands such as ssl-renew still prompt for le-mail when it's missing.
sudo -E bin/ee config set le-mail behat-ci@example.org
if [ -n "$EE_RCLONE_PATH" ]; then
sudo -E bin/ee config set rclone-path "$EE_RCLONE_PATH"
sudo -E bin/ee config set no-of-backups 3
fi
# EasyEngine's first run replaces the installed docker-compose with 2.27.0, so that's what the suite runs on, as on a fresh install.
# That download doesn't use curl --fail, so a GitHub error page can replace the binary; fetch 2.27.0 again then.
if ! docker-compose version; then
sudo curl -fsSL --retry 5 "https://github.com/docker/compose/releases/download/v2.27.0/docker-compose-linux-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose version
fi


- name: Run Behat
working-directory: easyengine
shell: 'script -q -e -c "bash {0}"'
# A hung step (e.g. a command stopped on the TTY) should still leave logs.
timeout-minutes: 75
run: |
set -e
mkdir -p "$RUNNER_TEMP/behat/junit"
base="$EXCLUDE_TAGS"
behat=(sudo -E vendor/bin/behat --strict --tags="${base}&&${{ matrix.tags }}"
--format=progress --out=std
--format=junit --out="$RUNNER_TEMP/behat/junit"
--format=pretty --out="$RUNNER_TEMP/behat/pretty.log")
"${behat[@]}"

- name: Collect logs
if: always()
run: |
out="$RUNNER_TEMP/behat"
mkdir -p "$out"
sudo cp /opt/easyengine/logs/ee.log "$out/" 2>/dev/null || echo 'No ee.log.'
sudo stat -c '%a %U:%G %n' /opt/easyengine /opt/easyengine/logs > "$out/dirs.txt" 2>/dev/null || true
sudo docker ps -a --format '{{.Names}}\t{{.Image}}\t{{.Status}}' > "$out/containers.txt" || true
sudo chown -R "$USER" "$out"
# One block per failed scenario, from the junit files.
python3 - "$out" <<'PY'
import glob, re, sys, xml.etree.ElementTree as ET
out = sys.argv[1]
lines = []
for f in sorted(glob.glob(out + '/junit/*.xml')):
# Behat copies raw output (docker logs with ANSI escapes) into the junit file; XML 1.0 forbids those bytes.
data = re.sub(rb'[\x00-\x08\x0b\x0c\x0e-\x1f]', b'', open(f, 'rb').read())
for case in ET.fromstring(data).iter('testcase'):
for bad in list(case.findall('failure')) + list(case.findall('error')):
lines.append('### %s :: %s\n%s\n%s\n' % (case.get('classname'), case.get('name'), bad.get('message') or '', bad.text or ''))
open(out + '/failures.txt', 'w').write('\n'.join(lines) or 'No failures.\n')
print('%d failed scenarios' % len(lines))
PY
head -c 20000 "$out/failures.txt"


- name: Upload results
if: always()
uses: actions/upload-artifact@v7
with:
name: behat-${{ matrix.lane }}-php${{ matrix.php }}
path: ${{ runner.temp }}/behat
retention-days: 14
Loading