Skip to content
Draft
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
32 changes: 32 additions & 0 deletions .gherkin-lintrc
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
{
"file-name": [
"on",
{
"style": "kebab-case"
}
],
"indentation": [
"on",
{
"Feature": 0,
"Background": 2,
"Scenario": 2,
"Examples": 4,
"Step": 4,
"given": 4,
"example": 6,
"and": 4
}
],
"no-dupe-feature-names": "on",
"no-dupe-scenario-names": "off",
"no-empty-file": "on",
"no-files-without-scenarios": "on",
"no-multiple-empty-lines": "off",
"no-partially-commented-tag-lines": "on",
"no-trailing-spaces": "off",
"no-unnamed-features": "on",
"no-unnamed-scenarios": "on",
"no-scenario-outlines-without-examples": "on",
"use-and": "on"
}
57 changes: 57 additions & 0 deletions .readme-partials/USING.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,12 +11,14 @@ To make use of the WP-CLI testing framework, you need to complete the following
"behat": "run-behat-tests",
"behat-rerun": "rerun-behat-tests",
"lint": "run-linter-tests",
"lint-gherkin": "run-gherkin-lint-tests",
"phpcs": "run-phpcs-tests",
"phpcbf": "run-phpcbf-cleanup",
"phpunit": "run-php-unit-tests",
"prepare-tests": "install-package-tests",
"test": [
"@lint",
"@lint-gherkin",
"@phpcs",
"@phpunit",
"@behat"
Expand DownExpand Up@@ -90,6 +92,7 @@ You can use the following commands to control the tests:
* `composer prepare-tests` - Set up the database that is needed for running the functional tests. This is only needed once.
* `composer test` - Run all test suites.
* `composer lint` - Run only the linting test suite.
* `composer lint-gherkin` - Run only the Gherkin linter over the feature files.
* `composer phpcs` - Run only the code sniffer test suite.
* `composer phpcbf` - Run only the code sniffer cleanup.
* `composer phpunit` - Run only the unit test suite.
Expand All@@ -104,6 +107,53 @@ composer behat -- features/cli-info.feature

Prepending with the double dash is needed because the arguments would otherwise be sent to Composer itself, not the tool that Composer executes.

The same mechanism works for narrowing a run down further, or for bailing out early:
```bash
# A single scenario, identified by the line it starts on.
composer behat -- features/cli-info.feature:12

# Every scenario carrying a given tag.
composer behat -- --tags=@require-wp-5.0

# Stop at the first failing scenario instead of running the whole suite.
composer behat -- --stop-on-failure

# Re-run only the scenarios that failed the last time.
composer behat-rerun
```

### Linting the feature files

`composer lint-gherkin` checks `features/` with
[gherkin-lint-plus](https://www.npmjs.com/package/gherkin-lint-plus), against the
`.gherkin-lintrc` ruleset shipped with this package. A project that needs
different rules can override it by committing its own `.gherkin-lintrc`.

The linter is a Node package, so it is run through `npx` and needs Node.js 20 or
later. Where `npx` is not available the check reports that it is skipping, rather
than failing a suite that is otherwise entirely PHP. Its version is pinned in
this package's `package.json`, which exists only to hold that pin.

### Controlling the amount of output

Two environment variables make the test tools less chatty. Both are unset by default, which leaves the output exactly as it has always been.

- `NO_COLOR` (the [no-color.org](https://no-color.org/) convention) stops the runners from forcing ANSI color codes on, and leaves the decision to each tool's own terminal detection. Set this when capturing output to a file or a pipe, where the escape sequences are noise.
- `WP_CLI_TEST_QUIET` switches the reporters to their most compact form: PHP_CodeSniffer reports one `file:line:col` line per violation with no progress ticker, PHPStan reports one `file:line:message` line per error with no progress bar and no result table. Behat's own output is already minimal, so it is unaffected.

`NO_COLOR` also covers the Gherkin linter, which colors its report unconditionally and has no plain output format of its own.

```bash
NO_COLOR=1 WP_CLI_TEST_QUIET=1 composer phpstan
```

This is worth setting permanently in environments that read the output back rather than display it, such as an AI coding agent's shell:

```bash
export NO_COLOR=1
export WP_CLI_TEST_QUIET=1
```

### Controlling the test environment

#### WordPress Version
Expand All@@ -119,6 +169,13 @@ Here's how to run your tests against the latest trunk version of WordPress:
WP_VERSION=trunk composer behat
```

Resolving `latest`, or a `X.Y` version without a patch number, needs the
WordPress versions data, which is fetched once and cached in the system temp
directory for a day. Repeated runs do not repeat the request, and a run without
connectivity falls back to the last known copy.
`WP_CLI_TEST_WP_VERSION_CACHE_TTL` sets the lifetime of that cache in seconds;
`0` fetches it every time.

#### WordPress Archive

Instead of downloading WordPress from WordPress.org, you can run the tests against an arbitrary
Expand Down
57 changes: 57 additions & 0 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,12 +22,14 @@ To make use of the WP-CLI testing framework, you need to complete the following
"behat": "run-behat-tests",
"behat-rerun": "rerun-behat-tests",
"lint": "run-linter-tests",
"lint-gherkin": "run-gherkin-lint-tests",
"phpcs": "run-phpcs-tests",
"phpcbf": "run-phpcbf-cleanup",
"phpunit": "run-php-unit-tests",
"prepare-tests": "install-package-tests",
"test": [
"@lint",
"@lint-gherkin",
"@phpcs",
"@phpunit",
"@behat"
Expand DownExpand Up@@ -101,6 +103,7 @@ You can use the following commands to control the tests:
* `composer prepare-tests` - Set up the database that is needed for running the functional tests. This is only needed once.
* `composer test` - Run all test suites.
* `composer lint` - Run only the linting test suite.
* `composer lint-gherkin` - Run only the Gherkin linter over the feature files.
* `composer phpcs` - Run only the code sniffer test suite.
* `composer phpcbf` - Run only the code sniffer cleanup.
* `composer phpunit` - Run only the unit test suite.
Expand All@@ -115,6 +118,53 @@ composer behat -- features/cli-info.feature

Prepending with the double dash is needed because the arguments would otherwise be sent to Composer itself, not the tool that Composer executes.

The same mechanism works for narrowing a run down further, or for bailing out early:
```bash
# A single scenario, identified by the line it starts on.
composer behat -- features/cli-info.feature:12

# Every scenario carrying a given tag.
composer behat -- --tags=@require-wp-5.0

# Stop at the first failing scenario instead of running the whole suite.
composer behat -- --stop-on-failure

# Re-run only the scenarios that failed the last time.
composer behat-rerun
```

### Linting the feature files

`composer lint-gherkin` checks `features/` with
[gherkin-lint-plus](https://www.npmjs.com/package/gherkin-lint-plus), against the
`.gherkin-lintrc` ruleset shipped with this package. A project that needs
different rules can override it by committing its own `.gherkin-lintrc`.

The linter is a Node package, so it is run through `npx` and needs Node.js 20 or
later. Where `npx` is not available the check reports that it is skipping, rather
than failing a suite that is otherwise entirely PHP. Its version is pinned in
this package's `package.json`, which exists only to hold that pin.

### Controlling the amount of output

Two environment variables make the test tools less chatty. Both are unset by default, which leaves the output exactly as it has always been.

- `NO_COLOR` (the [no-color.org](https://no-color.org/) convention) stops the runners from forcing ANSI color codes on, and leaves the decision to each tool's own terminal detection. Set this when capturing output to a file or a pipe, where the escape sequences are noise.
- `WP_CLI_TEST_QUIET` switches the reporters to their most compact form: PHP_CodeSniffer reports one `file:line:col` line per violation with no progress ticker, PHPStan reports one `file:line:message` line per error with no progress bar and no result table. Behat's own output is already minimal, so it is unaffected.

`NO_COLOR` also covers the Gherkin linter, which colors its report unconditionally and has no plain output format of its own.

```bash
NO_COLOR=1 WP_CLI_TEST_QUIET=1 composer phpstan
```

This is worth setting permanently in environments that read the output back rather than display it, such as an AI coding agent's shell:

```bash
export NO_COLOR=1
export WP_CLI_TEST_QUIET=1
```

### Controlling the test environment

#### WordPress Version
Expand All@@ -130,6 +180,13 @@ Here's how to run your tests against the latest trunk version of WordPress:
WP_VERSION=trunk composer behat
```

Resolving `latest`, or a `X.Y` version without a patch number, needs the
WordPress versions data, which is fetched once and cached in the system temp
directory for a day. Repeated runs do not repeat the request, and a run without
connectivity falls back to the last known copy.
`WP_CLI_TEST_WP_VERSION_CACHE_TTL` sets the lifetime of that cache in seconds;
`0` fetches it every time.

#### WordPress Archive

Instead of downloading WordPress from WordPress.org, you can run the tests against an arbitrary
Expand Down
95 changes: 84 additions & 11 deletions bin/run-behat-tests
Original file line numberDiff line numberDiff line change
Expand Up@@ -97,22 +97,90 @@ if [ -n "${WP_CLI_TEST_CORE_ZIP-}" ] && [ -z "${WP_VERSION-}" ]; then
export WP_VERSION=trunk
fi

# Everything WP_VERSION resolution needs is in one file: the wp-versions artifact
# maps every WordPress release to its status, with the current one marked
# "latest". Cache it, so that re-running a single scenario while iterating does
# not refetch it every time, and so that a run without connectivity can fall back
# to the last known answer instead of ending up with no version at all.
#
# Set WP_CLI_TEST_WP_VERSION_CACHE_TTL to 0 to always refetch.
WP_VERSIONS_URL="https://raw.githubusercontent.com/wp-cli/wp-cli-tests/artifacts/wp-versions.json"
WP_VERSIONS_CACHE_FILE="${TMPDIR:-/tmp}/wp-cli-test-wp-version-cache/wp-versions.json"
WP_VERSIONS_CACHE_TTL="${WP_CLI_TEST_WP_VERSION_CACHE_TTL:-86400}"

# Print the cached versions file if it is younger than the given number of
# seconds. A negative TTL accepts it at any age.
read_versions_cache() {
local ttl="$1"
local age

[ -s "${WP_VERSIONS_CACHE_FILE}" ] || return 1

if [ "${ttl}" -ge 0 ]; then
# PHP rather than `find -newermt`, which is not portable across
# GNU and BSD userlands. The Behat runner needs PHP anyway.
age=$(php -r 'echo time() - filemtime( $argv[1] );' "${WP_VERSIONS_CACHE_FILE}" 2>/dev/null)
case ${age} in
''|*[!0-9]*) return 1;;
esac
[ "${age}" -lt "${ttl}" ] || return 1
fi

cat "${WP_VERSIONS_CACHE_FILE}"
}

# Print the WordPress versions data, from the cache where possible. Warnings go
# to STDERR so that they cannot end up inside the returned JSON.
get_wp_versions() {
local json

json=$( read_versions_cache "${WP_VERSIONS_CACHE_TTL}" )
if [ -n "${json}" ]; then
printf '%s' "${json}"
return 0
fi

json=$( curl -s "${WP_VERSIONS_URL}" )

# Only cache a well-formed response; an error page is not one.
if echo "${json}" | jq -e 'type == "object" and length > 0' > /dev/null 2>&1; then
mkdir -p "$( dirname "${WP_VERSIONS_CACHE_FILE}" )" 2>/dev/null \
&& printf '%s' "${json}" > "${WP_VERSIONS_CACHE_FILE}" 2>/dev/null || true
printf '%s' "${json}"
return 0
fi

# Prefer a stale answer over no answer.
json=$( read_versions_cache -1 )
if [ -n "${json}" ]; then
echo "Warning: Could not fetch the WordPress versions data, falling back to the cached copy." >&2
printf '%s' "${json}"
return 0
fi

return 1
}

# Turn WP_VERSION into an actual number to make sure our tags work correctly.
if [ "${WP_VERSION-latest}" = "latest" ]; then
export WP_VERSION=$(curl -s https://api.wordpress.org/core/version-check/1.7/ | jq -r ".offers[0].current")
fi
WP_VERSION=$( get_wp_versions | jq -r 'to_entries | map( select( .value == "latest" ) ) | last | .key // empty' )

# Normalize WP_VERSION=X.Y.0 to X.Y (WordPress uses X.Y for the initial release, not X.Y.0).
# If WP_VERSION=X.Y (major.minor only), resolve to the latest available patch release.
if [[ "${WP_VERSION}" =~ ^([0-9]+\.[0-9]+)\.0$ ]]; then
if [ -z "${WP_VERSION}" ]; then
echo "Warning: Could not determine the latest WordPress version. Version-specific tags will not be filtered."
fi

export WP_VERSION
# Normalize WP_VERSION=X.Y.0 to X.Y (WordPress uses X.Y for the initial release,
# not X.Y.0). This asks for that specific release, so it must not fall through to
# the patch resolution below.
elif [[ "${WP_VERSION}" =~ ^([0-9]+\.[0-9]+)\.0$ ]]; then
export WP_VERSION="${BASH_REMATCH[1]}"
# If WP_VERSION=X.Y (major.minor only), resolve to the latest available patch release.
elif [[ "${WP_VERSION}" =~ ^[0-9]+\.[0-9]+$ ]]; then
WP_VERSIONS_JSON=$(curl -s https://raw.githubusercontent.com/wp-cli/wp-cli-tests/artifacts/wp-versions.json)
if [ -n "${WP_VERSIONS_JSON}" ]; then
RESOLVED_VERSION=$(echo "${WP_VERSIONS_JSON}" | jq -r --arg prefix "${WP_VERSION}." 'keys | map(select(startswith($prefix))) | sort_by(split(".") | map(tonumber)) | last // empty')
if [ -n "${RESOLVED_VERSION}" ]; then
export WP_VERSION="${RESOLVED_VERSION}"
fi
RESOLVED_VERSION=$( get_wp_versions | jq -r --arg prefix "${WP_VERSION}." 'keys | map( select( startswith( $prefix ) ) ) | sort_by( split(".") | map( tonumber ) ) | last // empty' )

if [ -n "${RESOLVED_VERSION}" ]; then
export WP_VERSION="${RESOLVED_VERSION}"
fi
fi

Expand DownExpand Up@@ -141,6 +209,11 @@ if [[ "${WP_CLI_TEST_COVERAGE}" == "true" ]] && vendor/bin/behat --help 2>/dev/n
BEHAT_EXTRA_ARGS+=('--xdebug')
fi

# Honor the NO_COLOR convention (https://no-color.org/).
if [ -n "${NO_COLOR}" ]; then
BEHAT_EXTRA_ARGS+=('--no-colors')
fi

# Run the functional tests.
FORMAT_ARGS=(--format progress)
for arg in "$@"; do
Expand Down
Loading
Loading