Skip to content

Commit 6e78ebc

Browse files
authored
Merge pull request #3 from woocommerce/ci/phpunit-without-wp-env
Run PHPUnit in CI without wp-env
2 parents ab7e7a7 + 63f8247 commit 6e78ebc

4 files changed

Lines changed: 156 additions & 49 deletions

File tree

.github/workflows/ci.yml

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,25 @@ jobs:
3434
run: composer run phpcs
3535

3636
phpunit:
37-
name: PHPUnit (integration, wp-env)
37+
name: PHPUnit (integration)
3838
runs-on: ubuntu-latest
39+
env:
40+
WP_CORE_DIR: /tmp/wordpress/src
41+
WP_TESTS_DIR: /tmp/wordpress/tests/phpunit
42+
43+
services:
44+
mysql:
45+
image: mysql:8.0
46+
env:
47+
MYSQL_ROOT_PASSWORD: root
48+
ports:
49+
- 3306:3306
50+
options: >-
51+
--health-cmd="mysqladmin ping"
52+
--health-interval=10s
53+
--health-timeout=5s
54+
--health-retries=3
55+
3956
steps:
4057
- name: Checkout
4158
uses: actions/checkout@v4
@@ -47,10 +64,8 @@ jobs:
4764
tools: composer:v2
4865
coverage: none
4966

50-
- name: Setup Node
51-
uses: actions/setup-node@v4
52-
with:
53-
node-version: '20'
67+
- name: Install SVN
68+
run: sudo apt-get install -y subversion
5469

5570
- name: Cache Composer
5671
uses: actions/cache@v4
@@ -62,17 +77,8 @@ jobs:
6277
- name: Composer install
6378
run: composer install --no-progress --prefer-dist --no-interaction
6479

65-
- name: Install wp-env
66-
run: npm install -g @wordpress/env
67-
68-
- name: Start wp-env
69-
run: wp-env start
70-
71-
- name: Run PHPUnit inside tests container
72-
run: |
73-
wp-env run tests-cli --env-cwd=wp-content/plugins/hey-woo-tests \
74-
-- php vendor/bin/phpunit --colors=always
80+
- name: Install WP test environment
81+
run: ./bin/install-wp-tests.sh wordpress_test root root 127.0.0.1 latest
7582

76-
- name: Capture wp-env logs on failure
77-
if: failure()
78-
run: wp-env logs tests
83+
- name: Run PHPUnit
84+
run: composer run test-unit

bin/install-wp-tests.sh

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#!/usr/bin/env bash
2+
# Set up a WordPress test environment with WooCommerce for PHPUnit.
3+
#
4+
# Usage: bin/install-wp-tests.sh <db-name> <db-user> <db-pass> [db-host] [wp-version] [skip-db-creation]
5+
#
6+
# Environment variables (override defaults):
7+
# WP_CORE_DIR — where WordPress core is installed (default: /tmp/wordpress/src)
8+
# WP_TESTS_DIR — where the WP PHPUnit suite lives (default: /tmp/wordpress/tests/phpunit)
9+
10+
set -euo pipefail
11+
12+
if [ $# -lt 3 ]; then
13+
echo "usage: $0 <db-name> <db-user> <db-pass> [db-host] [wp-version] [skip-db-creation]"
14+
exit 1
15+
fi
16+
17+
DB_NAME=$1
18+
DB_USER=$2
19+
DB_PASS=$3
20+
DB_HOST=${4:-localhost}
21+
WP_VERSION=${5:-latest}
22+
SKIP_DB_CREATE=${6:-false}
23+
24+
WP_CORE_DIR=${WP_CORE_DIR:-/tmp/wordpress/src}
25+
WP_TESTS_DIR=${WP_TESTS_DIR:-/tmp/wordpress/tests/phpunit}
26+
PLUGIN_DIR="$WP_CORE_DIR/wp-content/plugins"
27+
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
28+
29+
# Resolve "latest" to a concrete semver string (needed for the SVN tag path).
30+
if [ "$WP_VERSION" = "latest" ]; then
31+
WP_VERSION=$(curl -s https://api.wordpress.org/core/version-check/1.7/ \
32+
| grep -o '"version":"[^"]*"' | head -1 | cut -d'"' -f4)
33+
fi
34+
35+
# ── WordPress core ─────────────────────────────────────────────────────────────
36+
if [ ! -d "$WP_CORE_DIR/wp-includes" ]; then
37+
mkdir -p "$WP_CORE_DIR"
38+
curl -sL "https://wordpress.org/wordpress-${WP_VERSION}.tar.gz" \
39+
| tar xz -C "$WP_CORE_DIR" --strip-components=1
40+
fi
41+
42+
# ── WordPress PHPUnit test suite ───────────────────────────────────────────────
43+
if [ ! -d "$WP_TESTS_DIR/includes" ]; then
44+
mkdir -p "$WP_TESTS_DIR"
45+
svn co --quiet \
46+
"https://develop.svn.wordpress.org/tags/${WP_VERSION}/tests/phpunit" \
47+
"$WP_TESTS_DIR"
48+
fi
49+
50+
# ── wp-tests-config.php ────────────────────────────────────────────────────────
51+
# The WP test bootstrap at $WP_TESTS_DIR/includes/bootstrap.php detects the
52+
# tests/phpunit directory structure and looks for the config two levels up from
53+
# $WP_TESTS_DIR (i.e. dirname(dirname($WP_TESTS_DIR))/wp-tests-config.php).
54+
CONFIG_FILE="$(dirname "$(dirname "$WP_TESTS_DIR")")/wp-tests-config.php"
55+
if [ ! -f "$CONFIG_FILE" ]; then
56+
cat > "$CONFIG_FILE" <<PHP
57+
<?php
58+
// phpcs:disable
59+
\$table_prefix = 'wptests_';
60+
define( 'ABSPATH', '${WP_CORE_DIR}/' );
61+
define( 'DB_NAME', '${DB_NAME}' );
62+
define( 'DB_USER', '${DB_USER}' );
63+
define( 'DB_PASSWORD', '${DB_PASS}' );
64+
define( 'DB_HOST', '${DB_HOST}' );
65+
define( 'DB_CHARSET', 'utf8' );
66+
define( 'DB_COLLATE', '' );
67+
define( 'WP_TESTS_DOMAIN', 'example.org' );
68+
define( 'WP_TESTS_EMAIL', 'admin@example.org' );
69+
define( 'WP_TESTS_TITLE', 'Test Blog' );
70+
define( 'WP_PHP_BINARY', '$(command -v php)' );
71+
PHP
72+
fi
73+
74+
# ── Test database ──────────────────────────────────────────────────────────────
75+
if [ "$SKIP_DB_CREATE" = "false" ]; then
76+
# DB_HOST may be "host:port"; split so mysqladmin gets separate flags.
77+
MYSQL_HOST="${DB_HOST%%:*}"
78+
MYSQL_PORT="${DB_HOST##*:}"
79+
[ "$MYSQL_PORT" = "$MYSQL_HOST" ] && MYSQL_PORT="3306"
80+
mysqladmin create "$DB_NAME" \
81+
--user="$DB_USER" --password="$DB_PASS" \
82+
--host="$MYSQL_HOST" --port="$MYSQL_PORT" 2>/dev/null || true
83+
fi
84+
85+
# ── WooCommerce ────────────────────────────────────────────────────────────────
86+
if [ ! -f "$PLUGIN_DIR/woocommerce/woocommerce.php" ]; then
87+
mkdir -p "$PLUGIN_DIR"
88+
curl -sL "https://downloads.wordpress.org/plugin/woocommerce.latest-stable.zip" \
89+
-o /tmp/woocommerce.zip
90+
unzip -q /tmp/woocommerce.zip -d "$PLUGIN_DIR"
91+
rm /tmp/woocommerce.zip
92+
fi
93+
94+
# ── Plugin under test ──────────────────────────────────────────────────────────
95+
ln -sfn "$REPO_DIR" "$PLUGIN_DIR/hey-woo"

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"scripts": {
3333
"phpcs": "phpcs",
3434
"phpcbf": "phpcbf",
35-
"phpunit": "phpunit"
35+
"phpunit": "phpunit",
36+
"test-unit": "phpunit"
3637
}
3738
}

tests/integration/bootstrap.php

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@
22
/**
33
* PHPUnit bootstrap.
44
*
5-
* Designed to run inside the wp-env `tests-cli` container, where the
6-
* WordPress test suite is mounted at `/wordpress-phpunit` and the repo
7-
* is mounted (via the `mappings` entry in .wp-env.json) at
8-
* `/var/www/html/wp-content/plugins/hey-woo-tests`.
5+
* Supports two environments:
6+
*
7+
* 1. Local wp-env — run via `bin/check`. The tests-cli container mounts the
8+
* WP test suite at `/wordpress-phpunit` and the repo at
9+
* `wp-content/plugins/hey-woo-tests` (the --env-cwd target).
10+
*
11+
* 2. CI / bare PHP — run after `bin/install-wp-tests.sh`. Set WP_TESTS_DIR to
12+
* the path where the WP PHPUnit suite was installed; WooCommerce and the
13+
* plugin are installed into WP_PLUGIN_DIR by the install script.
914
*
1015
* @package HeyWoo\Tests
1116
*/
@@ -45,48 +50,48 @@ function hey_woo_tests_load_plugins() {
4550
require_once $wc_candidates[0];
4651

4752
require_once $plugin_dir . '/hey-woo/hey-woo.php';
53+
54+
// Prevent WooCommerce's own check_version() hook (plugins_loaded) from
55+
// running WC_Install::install() before we have a chance to set the HPOS
56+
// options below. We do the controlled install ourselves in the init hook.
57+
update_option( 'woocommerce_db_version', WC()->version );
4858
}
4959
tests_add_filter( 'muplugins_loaded', 'hey_woo_tests_load_plugins' );
5060

5161
/**
52-
* Activate WooCommerce explicitly so its install routine runs and the
53-
* HPOS tables (wc_orders / wc_orders_meta / wc_order_addresses /
54-
* wc_order_operational_data) exist before the first test query.
62+
* Run the WooCommerce install routine with the correct options set.
5563
*
56-
* HPOS is enabled by setting `woocommerce_custom_orders_table_enabled`
57-
* BEFORE `WC_Install::create_tables()` runs — the installer gates the
58-
* HPOS `dbDelta` on FeaturesController::feature_is_enabled(), which
59-
* reads that option. Data sync is disabled so orders land straight in
60-
* the HPOS tables without a parallel wp_postmeta write. Suppressing
61-
* the incompatible-plugin notice keeps the option update quiet.
64+
* Hooked to `init` (priority 0) so WordPress is fully bootstrapped before we
65+
* touch the database. Three things matter here:
6266
*
63-
* WC_Install::install() is invoked explicitly after activate_plugin()
64-
* because activate_plugin()'s activation-hook path runs asynchronously
65-
* in some WP paths; calling install() directly guarantees create_tables
66-
* fires once the option is in place.
67+
* 1. HPOS options must be set before WC_Install::create_tables() runs, otherwise
68+
* the HPOS order tables are not created.
69+
* 2. woocommerce_db_version must be deleted first so WC_Install::install() does
70+
* not bail out early thinking WC is already up-to-date.
71+
* 3. $wp_roles must be reset after create_roles() writes new capabilities to the
72+
* database. WP_Roles is a singleton that was already initialized before
73+
* create_roles() ran; without the reset, current_user_can() checks in tests
74+
* see the stale pre-install snapshot.
75+
* See https://core.trac.wordpress.org/ticket/28374
6776
*/
68-
function hey_woo_tests_activate_woocommerce() {
69-
if ( ! function_exists( 'activate_plugin' ) ) {
70-
return;
71-
}
72-
77+
function hey_woo_tests_install_woocommerce() {
7378
update_option( 'woocommerce_custom_orders_table_enabled', 'yes' );
7479
update_option( 'woocommerce_custom_orders_table_data_sync_enabled', 'no' );
7580
update_option( 'woocommerce_show_feature_enable_notice_custom_order_tables', 'no' );
7681

77-
$plugin_dir = defined( 'WP_PLUGIN_DIR' ) ? WP_PLUGIN_DIR : ABSPATH . 'wp-content/plugins';
78-
$wc_candidates = glob( $plugin_dir . '/woocommerce*/woocommerce.php' );
79-
if ( empty( $wc_candidates ) ) {
80-
return;
81-
}
82-
$relative = ltrim( str_replace( $plugin_dir, '', $wc_candidates[0] ), '/' );
83-
activate_plugin( $relative );
82+
// Allow install() to run by removing the version we pinned in muplugins_loaded.
83+
delete_option( 'woocommerce_db_version' );
8484

8585
if ( class_exists( 'WC_Install' ) ) {
8686
WC_Install::install();
8787
}
88+
89+
// Reload the WP_Roles singleton from the database so the capabilities added
90+
// by create_roles() (e.g. manage_woocommerce) are visible to tests.
91+
$GLOBALS['wp_roles'] = null; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
92+
wp_roles();
8893
}
89-
tests_add_filter( 'setup_theme', 'hey_woo_tests_activate_woocommerce' );
94+
tests_add_filter( 'init', 'hey_woo_tests_install_woocommerce', 0 );
9095

9196
require $_tests_dir . '/includes/bootstrap.php';
9297

0 commit comments

Comments
 (0)