Skip to content

Commit

Permalink
Merge pull request #677 from lucatume/v4-wploader-skip-install
Browse files Browse the repository at this point in the history
feat(WPLoader) add support for the skipInstall parameter
  • Loading branch information
lucatume authored Dec 6, 2023
2 parents 89ea68d + f15f33c commit 6d67c4f
Show file tree
Hide file tree
Showing 8 changed files with 463 additions and 219 deletions.
1 change: 1 addition & 0 deletions docs/modules/WPLoader.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ When used in this mode, the module supports the following configuration paramete
* `backupGlobalsExcludeList` - a list of global variables to exclude from the global environment backup. The list must be in the form of array, and it will be merged to the list of globals excluded by default.
* `backupStaticAttributes` - a boolean value to indicate if static attributes of classes should be backed up before each test. Defaults to `true`. The static attributes' backup involves serialization of the global state, plugins or themes that define classes developed to prevent serialization of the global state will cause the tests to fail. Set this parameter to `false` to disable the static attributes backup, or use a more refined approanch setting the `backupStaticAttributesExcludeList` parameter below. Note that a test case that is explicitly setting the `backupStaticAttributes` property will override this configuration parameter.
* `backupStaticAttributesExcludeList` - a list of classes to exclude from the static attributes backup. The list must be in the form of map from class names to the array of method names to exclude from the backup. See an example below.
* `skipInstall` - a boolean value to indicate if the WordPress installation should be skipped between runs, when already installed. Defaults to `false`. During boot, the `WPLoader` module will re-install WordPress and activate, on top of the fresh installation, any plugin and theme specified in the `plugins` and `theme` configuration parameters: this can be a time-consuming operation. Set this parameter to `true` to run the WordPress installation once and just load it on the following runs. To force the installation to run again, rerun the suite using the WPLoader module using the `--debug` flag or delete the `_wploader-state.sql` file in the suite directory. This configuration parameter is ignored when the `loadOnly` parameter is set to `true`.

This is an example of an integration suite configured to use the module:

Expand Down
9 changes: 2 additions & 7 deletions includes/core-phpunit/wp-tests-config.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,8 @@
* The `WP_MULTISITE` constant should not be defined at this stage: it will be picked up by the scripts from
* environment variables and defined in the tests bootstrap scripts.
*/
foreach ([
'WP_MULTISITE' => (int)$wpLoaderConfig['multisite'],
'WP_TESTS_SKIP_INSTALL' => 0
] as $envVar => $value) {
putenv($envVar . '=' . $value);
}
unset($envVar);
$value = (int)$wpLoaderConfig['multisite'];
putenv('WP_MULTISITE' . '=' . $value);

/*
* This file will be included a first time by the Core PHPUnit suite bootstrap file, and then
Expand Down
138 changes: 89 additions & 49 deletions src/Module/WPLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ class WPLoader extends Module
* backupGlobalsExcludeList?: string[],
* backupStaticAttributes?: bool,
* backupStaticAttributesExcludeList?: array<string,string[]>,
* skipInstall?: bool,
* }
*/
protected array $config = [
Expand Down Expand Up @@ -160,6 +161,7 @@ class WPLoader extends Module
'backupGlobalsExcludeList' => [],
'backupStaticAttributes' => true,
'backupStaticAttributesExcludeList' => [],
'skipInstall' => false
];

private string $wpBootstrapFile;
Expand All @@ -169,6 +171,7 @@ class WPLoader extends Module
private string $installationOutput = '';
private bool $earlyExit = true;
private ?DatabaseInterface $db = null;
private ?CodeExecutionFactory $codeExecutionFactory = null;

public function _getBootstrapOutput(): string
{
Expand Down Expand Up @@ -271,6 +274,14 @@ protected function validateConfig(): void
);
}

if (isset($this->config['skipInstall'])
&& !is_bool($this->config['skipInstall'])) {
throw new ModuleConfigException(
__CLASS__,
'The `skipInstall` configuration parameter must be a boolean.'
);
}

parent::validateConfig();
}

Expand Down Expand Up @@ -330,6 +341,7 @@ public function _initialize(): void
* backupGlobalsExcludeList: string[],
* backupStaticAttributes: bool,
* backupStaticAttributesExcludeList: array<string,string[]>,
* skipInstall: bool
* } $config
*/
$config = $this->config;
Expand Down Expand Up @@ -526,16 +538,16 @@ private function loadWordPress(bool $loadOnly = false): void
* The value will first look at the `WP_PLUGIN_DIR` constant, then the `pluginsFolder` configuration parameter
* and will, finally, look in the default path from the WordPress root directory.
*
* @param string $path A relative path to append to te plugins directory absolute path.
*
* @return string The absolute path to the `pluginsFolder` path or the same with a relative path appended if `$path`
* is provided.
* @example
* ```php
* $plugins = $this->getPluginsFolder();
* $hello = $this->getPluginsFolder('hello.php');
* ```
*
* @param string $path A relative path to append to te plugins directory absolute path.
*
* @return string The absolute path to the `pluginsFolder` path or the same with a relative path appended if `$path`
* is provided.
*/
public function getPluginsFolder(string $path = ''): string
{
Expand All @@ -545,16 +557,16 @@ public function getPluginsFolder(string $path = ''): string
/**
* Returns the absolute path to the themes directory.
*
* @param string $path A relative path to append to te themes directory absolute path.
*
* @return string The absolute path to the `themesFolder` path or the same with a relative path appended if `$path`
* is provided.
* @example
* ```php
* $themes = $this->getThemesFolder();
* $twentytwenty = $this->getThemesFolder('/twentytwenty');
* ```
*
* @param string $path A relative path to append to te themes directory absolute path.
*
* @return string The absolute path to the `themesFolder` path or the same with a relative path appended if `$path`
* is provided.
*/
public function getThemesFolder(string $path = ''): string
{
Expand All @@ -573,30 +585,39 @@ private function installAndBootstrapInstallation(): void
{
$GLOBALS['wpLoaderConfig'] = $this->config;

$skipInstall = ($this->config['skipInstall'] ?? false)
&& !Debug::isEnabled()
&& $this->isWordPressInstalled();

Dispatcher::dispatch(self::EVENT_BEFORE_INSTALL, $this);

$isMultisite = $this->config['multisite'];
$plugins = (array)$this->config['plugins'];
if (!$skipInstall) {
putenv('WP_TESTS_SKIP_INSTALL=0');
$isMultisite = $this->config['multisite'];
$plugins = (array)$this->config['plugins'];

/*
* The bootstrap file will load the `wp-settings.php` one that will load plugins and the theme.
* Hook on the option to get the the active plugins to run the plugins' and theme activation
* in a separate process.
*/
if ($isMultisite) {
// Activate plugins and enable theme network-wide.
$activate = function () use (&$activate, $plugins): array {
remove_filter('pre_site_option_active_sitewide_plugins', $activate);
return $this->muActivatePluginsTheme($plugins);
};
PreloadFilters::addFilter('pre_site_option_active_sitewide_plugins', $activate);
/*
* The bootstrap file will load the `wp-settings.php` one that will load plugins and the theme.
* Hook on the option to get the the active plugins to run the plugins' and theme activation
* in a separate process.
*/
if ($isMultisite) {
// Activate plugins and enable theme network-wide.
$activate = function () use (&$activate, $plugins): array {
remove_filter('pre_site_option_active_sitewide_plugins', $activate);
return $this->muActivatePluginsTheme($plugins);
};
PreloadFilters::addFilter('pre_site_option_active_sitewide_plugins', $activate);
} else {
// Activate plugins and theme.
$activate = function () use (&$activate, $plugins): array {
remove_filter('pre_option_active_plugins', $activate);
return $this->activatePluginsTheme($plugins);
};
PreloadFilters::addFilter('pre_option_active_plugins', $activate);
}
} else {
// Activate plugins and theme.
$activate = function () use (&$activate, $plugins): array {
remove_filter('pre_option_active_plugins', $activate);
return $this->activatePluginsTheme($plugins);
};
PreloadFilters::addFilter('pre_option_active_plugins', $activate);
putenv('WP_TESTS_SKIP_INSTALL=1');
}

$this->includeCorePHPUniteSuiteBootstrapFile();
Expand All @@ -605,7 +626,9 @@ private function installAndBootstrapInstallation(): void

$this->disableUpdates();

$this->importDumps();
if (!$skipInstall) {
$this->importDumps();
}

Dispatcher::dispatch(self::EVENT_AFTER_BOOTSTRAP, $this);

Expand All @@ -623,7 +646,7 @@ private function activatePluginsSwitchThemeInSeparateProcess(): void
/** @var array<string> $plugins */
$plugins = (array)($this->config['plugins'] ?: []);
$multisite = (bool)($this->config['multisite'] ?? false);
$closuresFactory = $this->getClosuresFactory();
$closuresFactory = $this->getCodeExecutionFactory();

$jobs = array_combine(
array_map(static fn(string $plugin): string => 'plugin::' . $plugin, $plugins),
Expand Down Expand Up @@ -696,15 +719,15 @@ private function runBootstrapActions(): void
* This method gives access to the same factories provided by the
* [Core test suite](https://make.wordpress.org/core/handbook/testing/automated-testing/writing-phpunit-tests/).
*
* @return FactoryStore A factory store, proxy to get hold of the Core suite object
* factories.
*
* @example
* ```php
* $postId = $I->factory()->post->create();
* $userId = $I->factory()->user->create(['role' => 'administrator']);
* ```
*
* @return FactoryStore A factory store, proxy to get hold of the Core suite object
* factories.
*
* @link https://make.wordpress.org/core/handbook/testing/automated-testing/writing-phpunit-tests/
*/
public function factory(): FactoryStore
Expand Down Expand Up @@ -742,30 +765,34 @@ private function loadConfigFiles(): void
/**
* Returns the absolute path to the WordPress content directory.
*
* @param string $path An optional path to append to the content directory absolute path.
*
* @return string The content directory absolute path, or a path in it.
* @example
* ```php
* $content = $this->getContentFolder();
* $themes = $this->getContentFolder('themes');
* $twentytwenty = $this->getContentFolder('themes/twentytwenty');
* ```
*
* @param string $path An optional path to append to the content directory absolute path.
*
* @return string The content directory absolute path, or a path in it.
*/
public function getContentFolder(string $path = ''): string
{
return $this->installation->getContentDir($path);
}

private function getClosuresFactory(): CodeExecutionFactory
private function getCodeExecutionFactory(): CodeExecutionFactory
{
if ($this->codeExecutionFactory !== null) {
return $this->codeExecutionFactory;
}

$installationState = $this->installation->getState();
$wpConfigFilePath = $installationState instanceof Scaffolded ?
$installationState->getWpRootDir('/wp-config.php')
: $installationState->getWpConfigPath();

return new CodeExecutionFactory(
$this->codeExecutionFactory = new CodeExecutionFactory(
$this->getWpRootFolder(),
$this->config['domain'] ?: 'localhost',
[$wpConfigFilePath => CorePHPUnit::path('/wp-tests-config.php')],
Expand All @@ -774,6 +801,8 @@ private function getClosuresFactory(): CodeExecutionFactory
'wpLoaderConfig' => $this->config
]
);

return $this->codeExecutionFactory;
}

public function getInstallation(): Installation
Expand Down Expand Up @@ -940,20 +969,18 @@ private function activatePluginsTheme(array $plugins): array
{
$this->activatePluginsSwitchThemeInSeparateProcess();

/** @var DatabaseInterface $database */
$database = $this->db;

if ($this->config['theme']) {
// Refresh the theme related options.
if ($database === null) {
throw new ModuleException(
__CLASS__,
'Could not get database instance from installation.'
);
}
if ($database === null) {
throw new ModuleException(
__CLASS__,
'Could not get database instance from installation.'
);
}

update_option('template', $database->getOption('template'));
update_option('stylesheet', $database->getOption('stylesheet'));
if ($this->config['theme']) {
$database->updateOption('template', $database->getOption('template'));
$database->updateOption('stylesheet', $database->getOption('stylesheet'));
}

// Flush the cache to force the refetch of the options' value.
Expand Down Expand Up @@ -995,4 +1022,17 @@ private function muActivatePluginsTheme(array $plugins): array
// Format for site-wide active plugins is `[ 'plugin-slug/plugin.php' => timestamp ]`.
return array_combine($plugins, array_fill(0, count($plugins), time()));
}

private function isWordPressInstalled(): bool
{
if (!$this->db instanceof DatabaseInterface) {
return false;
}

try {
return !empty($this->db->getOption('siteurl'));
} catch (Throwable) {
return false;
}
}
}
2 changes: 1 addition & 1 deletion src/Process/StderrStream.php
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ public function getThrowable(): ?Throwable
'message' => $sourceError->message,
'file' => $sourceError->file,
'line' => $sourceError->line,
'trace' => $sourceError->trace,
'trace' => array_map(static fn(TraceEntry $t)=> $t->toArray(), $sourceError->trace),
'code' => 0, // The code is not available in the error log.
]);

Expand Down
24 changes: 24 additions & 0 deletions src/Process/StderrStream/TraceEntry.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,28 @@ class TraceEntry
public string $args = '';
public string $file = '';
public int $line = 0;

/**
* @return array{
* date: string,
* time: string,
* timezone: string,
* call: string,
* args: string,
* file: string,
* line: int
* }
*/
public function toArray(): array
{
return [
'date' => $this->date,
'time' => $this->time,
'timezone' => $this->timezone,
'call' => $this->call,
'args' => $this->args,
'file' => $this->file,
'line' => $this->line,
];
}
}
9 changes: 8 additions & 1 deletion src/WordPress/Database/MysqlDatabase.php
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,14 @@ public function import(string $dumpFilePath): int
public function dump(string $dumpFile): void
{
try {
$dump = new Mysqldump($this->dsn, $this->dbUser, $this->dbPassword);
$dump = new class($this->dsn, $this->dbUser, $this->dbPassword) extends Mysqldump {
public function start($filename = '')
{
$this->dumpSettings['add-drop-table'] = true;
$this->dumpSettings['add-drop-database'] = true;
return parent::start($filename);
}
};
$dump->start($dumpFile);
} catch (\Exception $e) {
throw new DbException("Failed to dump database: " . $e->getMessage(), DbException::FAILED_DUMP);
Expand Down
Loading

0 comments on commit 6d67c4f

Please sign in to comment.