Skip to content

Commit

Permalink
Merge pull request #755 from lucatume/v4-issue-753
Browse files Browse the repository at this point in the history
Explicitly set DB_ constants in loadOnly:true
  • Loading branch information
lucatume authored Sep 11, 2024
2 parents d2bc8de + eb1b896 commit 3026504
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 13 deletions.
25 changes: 22 additions & 3 deletions docs/modules/WPLoader.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
## WPLoader module

// @todo update this

A module to load WordPress and make its code available in tests.

Depending on the value of the `loadOnly` configuration parameter, the module will behave differently:
Expand All @@ -22,6 +20,11 @@ will:
* take care of running any test method in a database transaction rolled back after each test
* manage and clean up the global environment and context between tests

!!! note

The module will set the environment variable `WPBROWSER_LOAD_ONLY=0` when running in this mode. This environment variable
can be used to detect whether WordPress is being loaded by WPBrowser and in which mode.

When used in this mode, the module supports the following configuration parameters:

* `loadOnly` - `false` to load WordPress and run tests in a controlled environment.
Expand All @@ -45,7 +48,7 @@ When used in this mode, the module supports the following configuration paramete
* `phpBinary` - the path to the PHP binary to use to run tests. Defaults to the `WP_PHP_BINARY` constant.
* `language` - the language to use when loading WordPress. Equivalent to defining the `WPLANG` constant.
* `configFile` - a configuration file, or a set of configuration files, to load before the tests to further customize
and control the WordPress testing environment.
and control the WordPress testing environment. This file(s) will be loaded before the WordPress installation is loaded.
* `pluginsFolder` - the path to the plugins folder to use when loading WordPress. Equivalent to defining the
`WP_PLUGIN_DIR` constant. If both this parameter and the `WP_PLUGIN_DIR` parameter are set, the `WP_PLUGIN_DIR`
parameter will override the value of this one.
Expand Down Expand Up @@ -288,6 +291,11 @@ your site to run tests using the default configuration based on PHP built-in ser
The module will load WordPress from the location specified by the `wpRootFolder` parameter, relying
on [the WPDb module](WPDb.md) to manage the database state.

!!! note

The module will set the environment variable `WPBROWSER_LOAD_ONLY=1` when running in this mode. This environment variable
can be used to detect whether WordPress is being loaded by WPBrowser and in which mode.

When used in this mode, the module supports the following configuration parameters:

* `loadOnly` - `true` to load WordPress and make it available in the context of tests.
Expand All @@ -299,6 +307,17 @@ When used in this mode, the module supports the following configuration paramete
use a SQLite database. Alternatively, you can use the `dbName`, `dbUser`, `dbPassword`, `dbHost` configuration
parameters to specify the database connection details.
* `domain` - the domain to use when loading WordPress. Equivalent to defining the `WP_TESTS_DOMAIN` constant.
* `configFile` - a configuration file, or a set of configuration files, to load before the tests to further customize
and control the WordPress testing environment. This file(s) will be loaded before the WordPress installation is loaded.

!!! warning

The module will define the `DB_NAME`, `DB_USER`, `DB_PASSWORD` and `DB_HOST` constants in the context of loading WordPress.
This is done to allow the WordPress database connection to be configured using the `dbUrl` configuration parameter.
**The module will silence the warnings about the redeclaration of these constants**, but in some cases with stricter error
checking (e.g. Bedrock) this may not be enough. In those cases, you can use the `WPBROWSER_LOAD_ONLY` environment
variable to detect whether WordPress is being loaded by WPBrowser and in which mode and configured your installation
accordingly.

The following is an example of the module configuration to run end-to-end tests on the site served
at `http://localhost:8080` URL and served from the `/var/wordpress` directory:
Expand Down
4 changes: 3 additions & 1 deletion src/Module/WPLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -618,11 +618,13 @@ public function _loadWordPress(?bool $loadOnly = null): void
$this->loadConfigFiles();

if ($loadOnly) {
putenv('WPBROWSER_LOAD_ONLY=1');
Dispatcher::dispatch(self::EVENT_BEFORE_LOADONLY, $this);
$loadSandbox = new LoadSandbox($this->installation->getWpRootDir(), $this->config['domain']);
$loadSandbox->load();
$loadSandbox->load($this->db);
Dispatcher::dispatch(self::EVENT_AFTER_LOADONLY, $this);
} else {
putenv('WPBROWSER_LOAD_ONLY=0');
$this->installAndBootstrapInstallation();
}

Expand Down
24 changes: 23 additions & 1 deletion src/WordPress/LoadSandbox.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

namespace lucatume\WPBrowser\WordPress;

use lucatume\WPBrowser\Utils\MonkeyPatch;
use lucatume\WPBrowser\Utils\Property;
use lucatume\WPBrowser\WordPress\Database\DatabaseInterface;
use lucatume\WPBrowser\WordPress\Database\MysqlDatabase;

class LoadSandbox
{
private string $wpRootDir;
Expand All @@ -19,7 +24,7 @@ public function __construct(string $wpRootDir, private string $domain)
/**
* @throws InstallationException
*/
public function load(): void
public function load(?DatabaseInterface $db = null): void
{
$this->setUpServerVars();
PreloadFilters::addFilter('wp_fatal_error_handler_enabled', [$this, 'returnFalse'], 100);
Expand All @@ -28,9 +33,26 @@ public function load(): void
// Setting the `chunk_size` to `0` means the function will only be called when the output buffer is closed.
ob_start([$this, 'obCallback'], 0);

if ($db instanceof MysqlDatabase) {
define('DB_NAME', $db->getDbName());
define('DB_USER', $db->getDbUser());
define('DB_PASSWORD', $db->getDbPassword());
define('DB_HOST', $db->getDbHost());

// Silence errors about the redeclaration of the above `DB_` constants.
$previousErrorHandler = set_error_handler(callback: static function ($errno, $errstr) {
return $errno === E_WARNING
&& preg_match('/^Constant DB_(NAME|USER|PASSWORD|HOST) already defined/i', $errstr);
});
}

// Exceptions thrown during loading are not wrapped on purpose to remove debug overhead.
include_once $this->wpRootDir . '/wp-load.php';

if (!empty($previousErrorHandler)) {
set_error_handler($previousErrorHandler);
}

ob_end_clean();
// If this is reached, then WordPress has loaded correctly.
remove_filter('wp_fatal_error_handler_enabled', [$this, 'returnFalse'], 100);
Expand Down
21 changes: 13 additions & 8 deletions tests/_support/Traits/InstallationMocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@ trait InstallationMocks
/**
* @return array{0: string, 1: string}
*/
private function makeMockConfiguredInstallation(string $phpExtra = ''): array
private function makeMockConfiguredInstallation(string $phpExtra = '', array $overrides = []): array
{
$dbUser = Env::get('WORDPRESS_DB_USER');
$dbPassword = Env::get('WORDPRESS_DB_PASSWORD');
$dbLocalhostPort = Env::get('WORDPRESS_DB_LOCALHOST_PORT');
$dbName = Env::get('WORDPRESS_DB_NAME');
$dbUser = $overrides['dbUser'] ?? Env::get('WORDPRESS_DB_USER');
$dbPassword = $overrides['dbPassword'] ?? Env::get('WORDPRESS_DB_PASSWORD');
if(!isset($overrides['dbHost'])){
$dbLocalhostPort = $overrides['dbLocalhostPort'] ?? Env::get('WORDPRESS_DB_LOCALHOST_PORT');
$dbHost = '127.0.0.1:' . $dbLocalhostPort;
} else {
$dbHost = $overrides['dbHost'];
}
$dbName = $overrides['dbName'] ?? Env::get('WORDPRESS_DB_NAME');
$wpRootFolder = FS::tmpDir('wploader_', [
'wp-includes' => [
'version.php' => <<< PHP
Expand All @@ -34,7 +39,7 @@ private function makeMockConfiguredInstallation(string $phpExtra = ''): array
define('DB_NAME', '$dbName');
define('DB_USER', '$dbUser');
define('DB_PASSWORD', '$dbPassword');
define('DB_HOST', '127.0.0.1:$dbLocalhostPort');
define('DB_HOST', '$dbHost');
define('DB_CHARSET', 'utf8');
define('DB_COLLATE', '');
global \$table_prefix;
Expand All @@ -53,10 +58,10 @@ private function makeMockConfiguredInstallation(string $phpExtra = ''): array
'wp-load.php' => '<?php do_action("wp_loaded");',
]);
$dbUrl = sprintf(
'mysql://%s:%s@127.0.0.1:%d/%s',
'mysql://%s:%s@%s/%s',
$dbUser,
$dbPassword,
$dbLocalhostPort,
$dbHost,
$dbName
);

Expand Down
126 changes: 126 additions & 0 deletions tests/unit/lucatume/WPBrowser/Module/WPLoaderLoadOnlyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use Codeception\Test\Unit;
use lucatume\WPBrowser\Tests\Traits\Fork;
use lucatume\WPBrowser\Tests\Traits\InstallationMocks;
use lucatume\WPBrowser\Utils\Env;
use lucatume\WPBrowser\Utils\Filesystem as FS;

class WPLoaderLoadOnlyTest extends Unit
{
Expand Down Expand Up @@ -79,4 +81,128 @@ public function testWillLoadWordPressInInitializeWhenLoadOnlyIsFalse(): void
$this->assertTrue($module->_didLoadWordPress());
});
}

public function testWillDefineDBConstantsWhenLoadOnlyTrue(): void{
[$wpRootFolder] = $this->makeMockConfiguredInstallation('', [
'dbUser' => 'production_user',
'dbPassword' => 'production_password',
'dbHost' => '10.0.0.1:8876',
'dbName' => 'test_db',
]);
file_put_contents($wpRootFolder . '/wp-load.php', '<?php include_once __DIR__ . "/wp-config.php"; do_action("wp_loaded");');
$testDbUser = Env::get('WORDPRESS_DB_USER');
$testDbPassword = Env::get('WORDPRESS_DB_PASSWORD');
$testDbHost = '127.0.0.1:' . Env::get('WORDPRESS_DB_LOCALHOST_PORT');
$testDbName = Env::get('WORDPRESS_DB_NAME');
$testDbUrl = sprintf(
'mysql://%s:%s@%s/%s',
$testDbUser,
$testDbPassword,
$testDbHost,
$testDbName
);
$moduleContainer = new ModuleContainer(new Di(), []);
$module = new WPLoader($moduleContainer, [
'dbUrl' => $testDbUrl,
'wpRootFolder' => $wpRootFolder,
'loadOnly' => true,
]);

Fork::executeClosure(function () use ($testDbName, $testDbHost, $testDbPassword, $testDbUser, $module) {
// WordPress' functions are stubbed by wordpress-stubs in unit tests: override them to do something.
$did_actions = [];
uopz_set_return('do_action', static function ($action) use (&$did_actions) {
$did_actions[$action] = true;
}, true);
uopz_set_return('did_action', static function ($action) use (&$did_actions) {
return isset($did_actions[$action]);
}, true);
// Partial mocking the function that would load WordPress.
uopz_set_return(WPLoader::class, 'installAndBootstrapInstallation', function () {
return true;
}, true);

$module->_initialize();
$module->_beforeSuite();

$this->assertTrue($module->_didLoadWordPress());
$this->assertEquals($testDbUser, DB_USER);
$this->assertEquals($testDbPassword, DB_PASSWORD);
$this->assertEquals($testDbHost, DB_HOST);
$this->assertEquals($testDbName, DB_NAME);
$this->assertEquals('1', getenv('WPBROWSER_LOAD_ONLY'));
});
}

public function testWillLoadConfigFileWhenLoadOnlyTrue(): void{
[$wpRootFolder, $dbUrl] = $this->makeMockConfiguredInstallation();
$configDir = FS::tmpDir('config_', [
'test-config.php' => '<?php define("TEST_CONFIG", true);'
]);
$moduleContainer = new ModuleContainer(new Di(), []);
$module = new WPLoader($moduleContainer, [
'dbUrl' => $dbUrl,
'wpRootFolder' => $wpRootFolder,
'loadOnly' => true,
'configFile' => $configDir . '/test-config.php'
]);

Fork::executeClosure(function () use ($module) {
// WordPress' functions are stubbed by wordpress-stubs in unit tests: override them to do something.
$did_actions = [];
uopz_set_return('do_action', static function ($action) use (&$did_actions) {
$did_actions[$action] = true;
}, true);
uopz_set_return('did_action', static function ($action) use (&$did_actions) {
return isset($did_actions[$action]);
}, true);
// Partial mocking the function that would load WordPress.
uopz_set_return(WPLoader::class, 'installAndBootstrapInstallation', function () {
return true;
}, true);

$module->_initialize();
$module->_beforeSuite();

$this->assertTrue($module->_didLoadWordPress());
$this->assertTrue(defined('TEST_CONFIG'));
});
}

public function testWillLoadMultipleConfigFilesWhenLoadOnlyTrue(): void{
[$wpRootFolder, $dbUrl] = $this->makeMockConfiguredInstallation();
$configDir = FS::tmpDir('config_', [
'test-config.php' => '<?php define("TEST_CONFIG", true);',
'test-config2.php' => '<?php define("TEST_CONFIG2", true);'
]);
$moduleContainer = new ModuleContainer(new Di(), []);
$module = new WPLoader($moduleContainer, [
'dbUrl' => $dbUrl,
'wpRootFolder' => $wpRootFolder,
'loadOnly' => true,
'configFile' => [$configDir . '/test-config.php', $configDir . '/test-config2.php']
]);

Fork::executeClosure(function () use ($module) {
// WordPress' functions are stubbed by wordpress-stubs in unit tests: override them to do something.
$did_actions = [];
uopz_set_return('do_action', static function ($action) use (&$did_actions) {
$did_actions[$action] = true;
}, true);
uopz_set_return('did_action', static function ($action) use (&$did_actions) {
return isset($did_actions[$action]);
}, true);
// Partial mocking the function that would load WordPress.
uopz_set_return(WPLoader::class, 'installAndBootstrapInstallation', function () {
return true;
}, true);

$module->_initialize();
$module->_beforeSuite();

$this->assertTrue($module->_didLoadWordPress());
$this->assertTrue(defined('TEST_CONFIG'));
$this->assertTrue(defined('TEST_CONFIG2'));
});
}
}

0 comments on commit 3026504

Please sign in to comment.