Skip to content

Commit

Permalink
Add support for rsync-ing mu-plugins and adding object-cache.php duri…
Browse files Browse the repository at this point in the history
…ng testing (#390)

* Starting vip mu

* Stubbing for more

* Setup to install mu-plugins

* Disable object cache installation if memcached is not found

* Disable VIP GO cache purging

* Use the memcached directly

* Add additional helper methods for installation manaer

* Add additional helper methods for installation manager (init/theme/plugins)

* Adding MANTLE_TESTING_DEBUG to force debug mode with ease

* Pass debug mode to INSTALL_WP_TEST_DEBUG

* Exclude mu-plugins/object-cache from rsyncing
  • Loading branch information
srtfisher authored May 26, 2023
1 parent 735faca commit ec38db4
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 29 deletions.
5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@
}
},
"scripts": {
"lint": "@phpcs",
"lint": [
"@phpcs",
"@phpstan"
],
"lint:fix": "@phpcbf",
"merge": "monorepo-builder merge",
"phpcbf": "phpcbf --standard=./phpcs.xml .",
Expand Down
4 changes: 2 additions & 2 deletions src/mantle/support/class-collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -583,8 +583,8 @@ public function has( $key ) {
/**
* Concatenate values of a given key as a string.
*
* @param callable|string $value
* @param string|null $glue
* @param callable|string|null $value
* @param string|null $glue
* @return string
*/
public function implode( $value, $glue = null ) {
Expand Down
59 changes: 47 additions & 12 deletions src/mantle/testing/class-installation-manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,26 +57,19 @@ public function before( ?callable $callback ) {
* Define a callback to be invoked after installation.
*
* @param callable|null $callback Callback to invoke after installation.
* @param bool $append Whether to append the callback to the list or prepend it.
* @return static
*/
public function after( ?callable $callback ) {
public function after( ?callable $callback, bool $append = true ) {
if ( is_callable( $callback ) ) {
$this->after_install_callbacks[] = $callback;
$append
? $this->after_install_callbacks[] = $callback
: array_unshift( $this->after_install_callbacks, $callback );
}

return $this;
}

/**
* Define a callback to be invoked using the 'muplugins_loaded' hook.
*
* @param callable $callback Callback to invoke on 'muplugins_loaded'.
* @return static
*/
public function loaded( ?callable $callback ) {
return $this->on( 'muplugins_loaded', $callback );
}

/**
* Define a callback for a specific WordPress hook.
*
Expand All @@ -94,6 +87,48 @@ public function on( string $hook, ?callable $callback, int $priority = 10, int $
return $this;
}

/**
* Define a callback to be invoked using the 'muplugins_loaded' hook.
*
* @param callable $callback Callback to invoke on 'muplugins_loaded'.
* @return static
*/
public function loaded( ?callable $callback ) {
return $this->on( 'muplugins_loaded', $callback );
}

/**
* Define a callback to be invoked on 'init'.
*
* @param callable $callback Callback to invoke on 'init'.
* @return static
*/
public function init( ?callable $callback ) {
return $this->loaded(
fn () => $this->on( 'init', $callback )
);
}

/**
* Define the active theme to be set after the installation is loaded.
*
* @param string $theme Theme name.
* @return static
*/
public function theme( string $theme ) {
return $this->loaded( fn () => switch_theme( $theme ) );
}

/**
* Define the active plugins to be set after the installation is loaded.
*
* @param array<int, string> $plugins Plugin files.
* @return static
*/
public function plugins( array $plugins ) {
return $this->loaded( fn () => update_option( 'active_plugins', $plugins ) );
}

/**
* Install the Mantle Testing Framework.
*
Expand Down
44 changes: 33 additions & 11 deletions src/mantle/testing/class-utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

use Mantle\Support\Str;
use Mantle\Testing\Doubles\Spy_REST_Server;

use function Mantle\Support\Helpers\collect;
use function Termwind\render;

require_once __DIR__ . '/concerns/trait-output-messages.php';
Expand Down Expand Up @@ -255,10 +257,14 @@ public static function env( string $variable, $default ) {
* `escapeshellarg()` doesn't fit here because the script is expecting
* unquoted arguments.
*
* @param string $string String to sanitize.
* @param string|bool $string String to sanitize.
* @return string
*/
public static function shell_safe( string $string ): string {
public static function shell_safe( string|bool $string ): string {
if ( is_bool( $string ) ) {
return $string ? 'true' : 'false';
}

return empty( trim( $string ) ) ? "''" : $string;
}

Expand All @@ -269,18 +275,30 @@ public static function shell_safe( string $string ): string {
* not install the WordPress database.
*
* @param string $directory Directory to install WordPress in.
* @param bool $install_vip_mu_plugins Whether to install VIP MU plugins.
* @param bool $install_object_cache Whether to install the object cache drop-in.
*/
public static function install_wordpress( string $directory ): void {
public static function install_wordpress( string $directory, bool $install_vip_mu_plugins = false, bool $install_object_cache = false ) {
$branch = static::env( 'MANTLE_CI_BRANCH', 'HEAD' );

$command = sprintf(
'export WP_CORE_DIR=%s && curl -s %s | bash -s %s %s %s %s %s %s',
'export WP_CORE_DIR=%s WP_MULTISITE=%s INSTALL_WP_TEST_DEBUG=%s && curl -s %s | bash -s %s',
$directory,
'https://raw.githubusercontent.com/alleyinteractive/mantle-ci/HEAD/install-wp-tests.sh',
static::shell_safe( defined( 'DB_NAME' ) ? DB_NAME : static::env( 'WP_DB_NAME', static::DEFAULT_DB_NAME ) ),
static::shell_safe( defined( 'DB_USER' ) ? DB_USER : static::env( 'WP_DB_USER', static::DEFAULT_DB_USER ) ),
static::shell_safe( defined( 'DB_PASSWORD' ) ? DB_PASSWORD : static::env( 'WP_DB_PASSWORD', static::DEFAULT_DB_PASSWORD ) ),
static::shell_safe( defined( 'DB_HOST' ) ? DB_HOST : static::env( 'WP_DB_HOST', static::DEFAULT_DB_HOST ) ),
static::shell_safe( static::env( 'WP_VERSION', 'latest' ) ),
static::shell_safe( static::env( 'WP_SKIP_DB_CREATE', 'false' ) ),
static::shell_safe( static::env( 'WP_MULTISITE', '0' ) ),
static::shell_safe( static::is_debug_mode() ),
"https://raw.githubusercontent.com/alleyinteractive/mantle-ci/{$branch}/install-wp-tests.sh",
collect(
[
static::shell_safe( defined( 'DB_NAME' ) ? DB_NAME : static::env( 'WP_DB_NAME', static::DEFAULT_DB_NAME ) ),
static::shell_safe( defined( 'DB_USER' ) ? DB_USER : static::env( 'WP_DB_USER', static::DEFAULT_DB_USER ) ),
static::shell_safe( defined( 'DB_PASSWORD' ) ? DB_PASSWORD : static::env( 'WP_DB_PASSWORD', static::DEFAULT_DB_PASSWORD ) ),
static::shell_safe( defined( 'DB_HOST' ) ? DB_HOST : static::env( 'WP_DB_HOST', static::DEFAULT_DB_HOST ) ),
static::shell_safe( static::env( 'WP_VERSION', 'latest' ) ),
static::shell_safe( static::env( 'WP_SKIP_DB_CREATE', 'false' ) ),
static::shell_safe( $install_vip_mu_plugins ? 'true' : 'false' ),
static::shell_safe( $install_object_cache ? 'true' : 'false' ),
]
)->implode( ' ' ),
);

$retval = 0;
Expand All @@ -299,6 +317,10 @@ public static function install_wordpress( string $directory ): void {
* @return bool
*/
public static function is_debug_mode(): bool {
if ( defined( 'MANTLE_TESTING_DEBUG' ) && MANTLE_TESTING_DEBUG ) {
return true;
}

return ! empty(
array_intersect(
(array) ( $_SERVER['argv'] ?? [] ), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
Expand Down
82 changes: 79 additions & 3 deletions src/mantle/testing/concerns/trait-rsync-installation.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
* the theme to a WordPress installation without needing to run a bash script.
*
* After the rsync is complete, PHPUnit will be rerun from the new location.
*
* @mixin \Mantle\Testing\Installation_Manager
*/
trait Rsync_Installation {
use Conditionable;
Expand All @@ -44,27 +46,42 @@ trait Rsync_Installation {
protected ?string $rsync_from = null;

/**
* Subdirectory from the parent folder being rsynced to the previous working
* Subdirectory from the parent folder being rsync-ed to the previous working
* directory.
*
* @var string
*/
protected ?string $rsync_subdir = '';

/**
* Flag to install the VIP MU plugins.
*
* @var boolean
*/
protected bool $install_vip_mu_plugins = false;

/**
* Flag to install a Memcache object cache drop-in.
*
* @var boolean
*/
protected bool $install_object_cache = false;

/**
* Exclusions to be used when rsyncing the codebase.
*
* @var string[]
*/
protected array $rsync_exclusions = [
'.buddy-tests',
'.composer',
'.buddy',
'.composer',
'.git',
'.npm',
'.phpcs',
'.turbo',
'node_modules',
'phpstan.neon',
];

/**
Expand Down Expand Up @@ -131,6 +148,61 @@ public function maybe_rsync_wp_content(): static {
return $this->maybe_rsync( '/', dirname( getcwd(), 3 ) );
}

/**
* Attempt to install VIP's built mu-plugins into the codebase.
*
* Will only be applied if the codebase is not already within a WordPress and
* is being rsync-ed to one.
*
* @param bool $install Install VIP's built mu-plugins into the codebase.
* @return static
*/
public function with_vip_mu_plugins( bool $install = true ): static {
if ( $this->is_within_wordpress_install() ) {
return $this;
}

$this->rsync_exclusions[] = 'mu-plugins';

$this->install_vip_mu_plugins = $install;

return $this;
}

/**
* Attempt to install the object cache drop-in into the codebase.
*
* Will only be applied if the codebase is not already within a WordPress and
* is being rsync-ed to one.
*
* @param bool $install Install the object cache drop-in into the codebase.
* @return static
*/
public function with_object_cache( bool $install = true ): static {
if ( $this->is_within_wordpress_install() ) {
return $this;
}

// Check if Memcached is installed.
if ( ! class_exists( \Memcached::class ) ) {
// Allow the object cache to be forcefully required.
if ( Utils::env( 'MANTLE_REQUIRE_OBJECT_CACHE', false ) ) {
Utils::error( 'Memcached is not installed. Cannot install object cache. Exiting...' );
exit( 1 );
}

Utils::error( 'Memcached is not installed. Cannot install object cache. Skipping...' );

return $this;
}

$this->rsync_exclusions[] = 'object-cache.php';

$this->install_object_cache = $install;

return $this;
}

/**
* Maybe rsync the codebase as a plugin within WordPress.
*
Expand Down Expand Up @@ -236,7 +308,11 @@ protected function perform_rsync_testsuite() {
exit( 1 );
}

Utils::install_wordpress( $base_install_path );
Utils::install_wordpress(
directory: $base_install_path,
install_vip_mu_plugins: $this->install_vip_mu_plugins,
install_object_cache: $this->install_object_cache,
);

Utils::success(
"WordPress installed at <em>{$base_install_path}</em>",
Expand Down
5 changes: 5 additions & 0 deletions src/mantle/testing/wordpress-bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@
define( 'WP_MEMORY_LIMIT', -1 );
define( 'WP_MAX_MEMORY_LIMIT', -1 );

// Disable VIP GO cache purging during testing.
if ( ! defined( 'VIP_GO_DISABLE_CACHE_PURGING' ) ) {
define( 'VIP_GO_DISABLE_CACHE_PURGING', true );
}

$PHP_SELF = '/index.php';
$GLOBALS['PHP_SELF'] = '/index.php';
$_SERVER['PHP_SELF'] = '/index.php';
Expand Down
3 changes: 3 additions & 0 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
define( 'MANTLE_PHPUNIT_INCLUDES_PATH', __DIR__ . '/includes' );
define( 'MANTLE_PHPUNIT_FIXTURES_PATH', __DIR__ . '/fixtures' );
define( 'MANTLE_PHPUNIT_TEMPLATE_PATH', __DIR__ . '/template-parts' );
define( 'MANTLE_TESTING_DEBUG', true );

\Mantle\Testing\manager()
->maybe_rsync_plugin()
->with_object_cache()
->with_vip_mu_plugins()
->install();

0 comments on commit ec38db4

Please sign in to comment.