Skip to content

Commit

Permalink
fix(Modules/WPLoader) authoritatively set DB_ constants in loadOnly:true
Browse files Browse the repository at this point in the history
  • Loading branch information
lucatume committed Sep 10, 2024
1 parent 75d5a72 commit aad84df
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 45 deletions.
21 changes: 19 additions & 2 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 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 @@ -300,6 +308,15 @@ When used in this mode, the module supports the following configuration paramete
parameters to specify the database connection details.
* `domain` - the domain to use when loading WordPress. Equivalent to defining the `WP_TESTS_DOMAIN` constant.

!!! 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
2 changes: 2 additions & 0 deletions 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($this->db);
Dispatcher::dispatch(self::EVENT_AFTER_LOADONLY, $this);
} else {
putenv('WPBROWSER_LOAD_ONLY=0');
$this->installAndBootstrapInstallation();
}

Expand Down
37 changes: 2 additions & 35 deletions src/WordPress/LoadSandbox.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,61 +33,28 @@ public function load(?DatabaseInterface $db = null): 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);

// ISSUE #753
if ($db instanceof MysqlDatabase) {
// Define the `DB_` constants.
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 `DB_` constants.
// Silence errors about the redeclaration of the above `DB_` constants.
$previousErrorHandler = set_error_handler(callback: static function ($errno, $errstr) {
if ($errno === E_USER_ERROR && str_contains($errstr, 'Cannot redeclare') && str_contains($errstr, 'DB_')) {
if ($errno === E_WARNING && preg_match('/^Constant DB_(NAME|USER|PASSWORD|HOST) already defined/i', $errstr)) {
return true;
}

return false;
});

if(class_exists('\Roots\WPConfig\Config')){
$configArray = new class implements \ArrayAccess {
private $configMap = [];

public function offsetExists( $offset ) {
return isset( $this->configMap[ $offset ] );
}

public function offsetGet( $offset ) {
return $this->configMap[ $offset ];
}

public function offsetSet( $offset, $value ) {
if ( in_array( $offset, [ 'DB_NAME', 'DB_USER', 'DB_PASSWORD', 'DB_HOST' ], true ) ) {
return;
}
$this->configMap[ $offset ] = $value;
}

public function offsetUnset( $offset ) {
unset( $this->configMap[ $offset ] );
}
};

Property::setPrivateProperties( '\Roots\WPConfig\Config', [
'configMap' => $configArray
] );
}
// END ISSUE #753

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

// ISSUE #753
if (!empty($previousErrorHandler)) {
set_error_handler($previousErrorHandler);
}
// END ISSUE #753

ob_end_clean();
// If this is reached, then WordPress has loaded correctly.
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
54 changes: 54 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\WordPress\Database\MysqlDatabase;

class WPLoaderLoadOnlyTest extends Unit
{
Expand Down Expand Up @@ -79,4 +81,56 @@ 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'));
});
}
}

0 comments on commit aad84df

Please sign in to comment.