Skip to content

Commit

Permalink
feat(WPLoader) support the silentlyActivatePlugins conf param
Browse files Browse the repository at this point in the history
  • Loading branch information
lucatume committed Dec 6, 2023
1 parent a65926c commit 5c8e4d6
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 23 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).

## [unreleased] Unreleased

### Added

- The `WPLoader::silentlyActivatePlugins` configuration parameter to activate plugins without firing the `activated_plugin` action.

## [4.0.14] 2023-12-06;

### Added
Expand Down
1 change: 1 addition & 0 deletions docs/modules/WPLoader.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ When used in this mode, the module supports the following configuration paramete
`WP_PLUGIN_DIR` constant.
* `plugins` - a list of plugins to activate and load in the WordPress installation. Each plugin must be specified in a
format like `hello.php` or `my-plugin/my-plugin.php` format.
* `silentlyActivatePlugins` - a list of plugins to activate **silently**, without firing their activation hooks. Depending on the plugin, a silent activation might cause the plugin to not work correctly. The list must be in the same format as the `plugins` parameter and plugin should be activated silently only if they are not working correctly during normal activation and are known to work correctly when activated silently.
* `bootstrapActions` - a list of actions or callables to call **after** WordPress is loaded and before the tests run.
* `theme` - the theme to activate and load in the WordPress installation. The theme must be specified in slug format
like
Expand Down
49 changes: 46 additions & 3 deletions src/Module/WPLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ class WPLoader extends Module
* configFile: string|string[],
* pluginsFolder: string,
* plugins: string[],
* silentlyActivatePlugins: string[],
* bootstrapActions: string|string[],
* theme: string,
* AUTH_KEY: string,
Expand Down Expand Up @@ -141,6 +142,7 @@ class WPLoader extends Module
'configFile' => '',
'pluginsFolder' => '',
'plugins' => [],
'silentlyActivatePlugins' => [],
'bootstrapActions' => '',
'theme' => '',
'AUTH_KEY' => '',
Expand Down Expand Up @@ -193,6 +195,37 @@ protected function validateConfig(): void
$this->config['dbCharset'] = $this->config['DB_CHARSET'] ?? $this->config['dbCharset'] ?? '';
$this->config['dbCollate'] = $this->config['DB_COLLATE'] ?? $this->config['dbCollate'] ?? '';
$this->config['multisite'] = (bool)($this->config['WP_TESTS_MULTISITE'] ?? $this->config['multisite'] ?? false);

if (!(
is_array($this->config['plugins'])
&& Arr::containsOnly($this->config['plugins'], 'string'))
) {
throw new ModuleConfigException(
__CLASS__,
'The `plugins` configuration parameter must be an array of plugin names ' .
'in the my-plugin/plugin.php or plugin.php format.'
);
}

if (!(
is_array($this->config['silentlyActivatePlugins'])
&& Arr::containsOnly($this->config['silentlyActivatePlugins'], 'string'))
) {
throw new ModuleConfigException(
__CLASS__,
'The `silentlyActivatePlugins` configuration parameter must be an array of plugin names ' .
'in the my-plugin/plugin.php or plugin.php format.'
);
}

if (count(array_intersect($this->config['plugins'], $this->config['silentlyActivatePlugins']))) {
throw new ModuleConfigException(
__CLASS__,
'The `plugins` and `silentlyActivatePlugins` configuration parameters must not contain the ' .
'same plugins.'
);
}

$this->config['theme'] = $this->config['WP_TESTS_MULTISITE'] ?? $this->config['theme'] ?? '';

if (!is_string($this->config['theme'])) {
Expand Down Expand Up @@ -314,6 +347,7 @@ public function _initialize(): void
* configFile: string|string[],
* pluginsFolder: string,
* plugins: string[],
* silentlyActivatePlugins: string[],
* bootstrapActions: string|string[],
* theme: string,
* AUTH_KEY: string,
Expand Down Expand Up @@ -645,14 +679,23 @@ private function activatePluginsSwitchThemeInSeparateProcess(): void
{
/** @var array<string> $plugins */
$plugins = (array)($this->config['plugins'] ?: []);
$silentlyActivatePlugins = (array)($this->config['silentlyActivatePlugins'] ?: []);
$allPlugins = array_merge($plugins, $silentlyActivatePlugins);
$multisite = (bool)($this->config['multisite'] ?? false);
$closuresFactory = $this->getCodeExecutionFactory();
$silentFlags = array_merge(
array_fill(0, count($plugins), false),
array_fill(0, count($silentlyActivatePlugins), true)
);

$jobs = array_combine(
array_map(static fn(string $plugin): string => 'plugin::' . $plugin, $plugins),
array_map(static fn(string $plugin): string => 'plugin::' . $plugin, $allPlugins),
array_map(
static fn(string $plugin): Closure => $closuresFactory->toActivatePlugin($plugin, $multisite),
$plugins
static function (string $plugin, bool $silent) use ($closuresFactory, $multisite): Closure {
return $closuresFactory->toActivatePlugin($plugin, $multisite, $silent);
},
$allPlugins,
$silentFlags
)
);

Expand Down
9 changes: 5 additions & 4 deletions src/WordPress/CodeExecution/ActivatePluginAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,23 @@ public function __construct(
FileRequest $request,
string $wpRootDir,
string $plugin,
bool $multisite
bool $multisite,
bool $silent = false
) {
$request->setTargetFile($wpRootDir . '/wp-load.php')
->runInFastMode($wpRootDir)
->defineConstant('MULTISITE', $multisite)
->addAfterLoadClosure(fn() => $this->activatePlugin($plugin, $multisite));
->addAfterLoadClosure(fn() => $this->activatePlugin($plugin, $multisite, $silent));
$this->request = $request;
}

/**
* @throws InstallationException
*/
private function activatePlugin(string $plugin, bool $multisite): void
private function activatePlugin(string $plugin, bool $multisite, bool $silent = false): void
{
require_once ABSPATH . 'wp-admin/includes/plugin.php';
$activated = activate_plugin($plugin, '', $multisite);
$activated = activate_plugin($plugin, '', $multisite, $silent);
$activatedString = $multisite ? 'network activated' : 'activated';
$message = "Plugin $plugin could not be $activatedString.";

Expand Down
4 changes: 2 additions & 2 deletions src/WordPress/CodeExecution/CodeExecutionFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ public function toCheckIfWpIsInstalled(bool $multisite): Closure
->getClosure();
}

public function toActivatePlugin(string $plugin, bool $multisite): Closure
public function toActivatePlugin(string $plugin, bool $multisite, bool $silent = false): Closure
{
$request = $this->requestFactory->buildGetRequest()
->blockHttpRequests()
->setRedirectFiles($this->redirectFiles)
->addPresetGlobalVars($this->presetGlobalVars);

return (new ActivatePluginAction($request, $this->wpRootDir, $plugin, $multisite))
return (new ActivatePluginAction($request, $this->wpRootDir, $plugin, $multisite, $silent))
->getClosure();
}

Expand Down
177 changes: 163 additions & 14 deletions tests/unit/lucatume/WPBrowser/Events/Module/WPLoaderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
use lucatume\WPBrowser\WordPress\InstallationException;
use lucatume\WPBrowser\WordPress\InstallationState\InstallationStateInterface;
use lucatume\WPBrowser\WordPress\InstallationState\Scaffolded;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\TestResult;
use stdClass;
use tad\Codeception\SnapshotAssertions\SnapshotAssertions;
Expand Down Expand Up @@ -2396,16 +2397,7 @@ public function should_skip_installation_when_skip_install_is_true(): void
'woocommerce'
]
]);
if (!mkdir($wpRootDir . '/tests/some_test_suite', 0777, true)) {
throw new \RuntimeException('Failed to create the test suite folder.');
}
$moduleContainerConfig = [
'config' => [
'suite' => 'some_test_suite',
'path' => $wpRootDir . '/tests/some_test_suite'
]
];
$moduleConfig = [
$this->config = [
'wpRootFolder' => $wpRootDir,
'dbUrl' => $installationDb->getDbUrl(),
'tablePrefix' => 'test_',
Expand All @@ -2415,7 +2407,7 @@ public function should_skip_installation_when_skip_install_is_true(): void
];

// Run the module a first time: it should create the flag file indicating the database was installed.
$wpLoader = $this->module($moduleContainerConfig, $moduleConfig);
$wpLoader = $this->module();
$moduleSplObjectHash = spl_object_hash($wpLoader);
$this->assertInIsolation(
static function () use ($wpLoader, $moduleSplObjectHash) {
Expand Down Expand Up @@ -2463,7 +2455,7 @@ static function () use ($wpLoader, $moduleSplObjectHash) {
$this->assertEquals('twentytwenty', $checkDb->getOption('stylesheet'));

// Run a second time, this time the installation should be skipped.
$wpLoader = $this->module($moduleContainerConfig, $moduleConfig);
$wpLoader = $this->module();
$this->assertInIsolation(
static function () use ($moduleSplObjectHash, $wpLoader) {
$beforeInstallCalled = false;
Expand Down Expand Up @@ -2499,9 +2491,9 @@ static function () use ($moduleSplObjectHash, $wpLoader) {
);

// Now run in --debug mode, the installation should run again.
$wpLoader = $this->module($moduleContainerConfig, $moduleConfig);
$wpLoader = $this->module();
$this->assertInIsolation(
static function () use ($moduleSplObjectHash, $wpLoader) {
static function () use ($wpLoader) {
$beforeInstallCalled = false;
$afterInstallCalled = false;
$afterBootstrapCalled = false;
Expand Down Expand Up @@ -2539,4 +2531,161 @@ static function () use ($moduleSplObjectHash, $wpLoader) {
}
);
}

/**
* It should throw if silentlyActivatePlugins config parameter is not a list of strings
*
* @test
* @dataProvider notArrayOfStringsProvider
*/
public function should_throw_if_silently_activate_plugins_config_parameter_is_not_a_list_of_strings($input): void
{
$wpRootDir = Env::get('WORDPRESS_ROOT_DIR');
$db = (new Installation($wpRootDir))->getDb();
$this->config = [
'wpRootFolder' => $wpRootDir,
'dbUrl' => $db->getDbUrl(),
'silentlyActivatePlugins' => $input,
];

$this->expectException(ModuleConfigException::class);

$this->module();
}

/**
* It should throw if plugin appears in both plugins and silentlyActivatePlugins config parameters
*
* @test
*/
public function should_throw_if_plugin_appears_in_both_plugins_and_silently_activate_plugins_config_parameters(
): void
{
$wpRootDir = Env::get('WORDPRESS_ROOT_DIR');
$db = (new Installation($wpRootDir))->getDb();
$this->config = [
'wpRootFolder' => $wpRootDir,
'dbUrl' => $db->getDbUrl(),
'plugins' => ['woocommerce/woocommerce.php', 'my-plugin/plugin.php'],
'silentlyActivatePlugins' => ['foo-plugin/plugin.php', 'woocommerce/woocommerce.php'],
];

$this->expectException(ModuleConfigException::class);

$this->module();
}

/**
* It should fail to activate when plugins generate unexpected output
*
* @test
*/
public function should_fail_to_activate_when_plugins_generate_unexpected_output(): void
{
$wpRootDir = FS::tmpDir('wploader_');
$installation = Installation::scaffold($wpRootDir);
$dbName = Random::dbName();
$dbHost = Env::get('WORDPRESS_DB_HOST');
$dbUser = Env::get('WORDPRESS_DB_USER');
$dbPassword = Env::get('WORDPRESS_DB_PASSWORD');
$installationDb = new MysqlDatabase($dbName, $dbUser, $dbPassword, $dbHost, 'wp_');
$installation->configure($installationDb);
$this->copyOverContentFromTheMainInstallation($installation, [
'plugins' => [
'woocommerce'
]
]);
// Create a plugin that will raise a doing_it_wrong error on activation.
FS::mkdirp($wpRootDir . '/wp-content/plugins', [
'my-plugin' => [
'plugin.php' => <<< PHP
<?php
/** Plugin Name: DIW Plugin */
function activate_my_plugin(){
echo 'Something went wrong';
}
register_activation_hook( __FILE__, 'activate_my_plugin' );
PHP
]
]);

$this->config = [
'wpRootFolder' => $wpRootDir,
'dbUrl' => $installationDb->getDbUrl(),
'tablePrefix' => 'test_',
'plugins' => ['woocommerce/woocommerce.php', 'my-plugin/plugin.php'],
];

// Run a first initialization that should fail due to the doing_it_wrong error.
$wpLoader = $this->module();

$this->expectException(ModuleException::class);

$this->assertInIsolation(
static function () use ($wpLoader) {
$wpLoader->_initialize();
}
);
}

/**
* It should allow activating plugins silently
*
* @test
*/
public function should_allow_activating_plugins_silently(): void
{
$wpRootDir = FS::tmpDir('wploader_');
$installation = Installation::scaffold($wpRootDir);
$dbName = Random::dbName();
$dbHost = Env::get('WORDPRESS_DB_HOST');
$dbUser = Env::get('WORDPRESS_DB_USER');
$dbPassword = Env::get('WORDPRESS_DB_PASSWORD');
$installationDb = new MysqlDatabase($dbName, $dbUser, $dbPassword, $dbHost, 'wp_');
$installation->configure($installationDb);
$this->copyOverContentFromTheMainInstallation($installation, [
'plugins' => [
'woocommerce'
]
]);
// Create a plugin that will raise a doing_it_wrong error on activation.
FS::mkdirp($wpRootDir . '/wp-content/plugins', [
'my-plugin' => [
'plugin.php' => <<< PHP
<?php
/** Plugin Name: DIW Plugin */
function activate_my_plugin(){
echo 'Something went wrong';
update_option('my_plugin_activated', '__activated__');
}
register_activation_hook( __FILE__, 'activate_my_plugin' );
update_option('my_plugin_loaded', '__loaded__');
PHP
]
]);

$this->config = [
'wpRootFolder' => $wpRootDir,
'dbUrl' => $installationDb->getDbUrl(),
'tablePrefix' => 'test_',
'plugins' => ['woocommerce/woocommerce.php'],
'silentlyActivatePlugins' => ['my-plugin/plugin.php'],
];

// Run a first initialization that should fail due to the doing_it_wrong error.
$wpLoader = $this->module();

$this->assertInIsolation(
static function () use ($wpLoader) {
$wpLoader->_initialize();

assertEquals('', get_option('my_plugin_activated'));
assertEquals('__loaded__', get_option('my_plugin_loaded'));
}
);
}
}

0 comments on commit 5c8e4d6

Please sign in to comment.