Skip to content

Commit

Permalink
Merge pull request #675 from lucatume/v4-customizable-backup-setting
Browse files Browse the repository at this point in the history
feat(WPLoader) add global state control support, fix #673
  • Loading branch information
lucatume authored Nov 25, 2023
2 parents 5c9150e + deef59a commit eb6b5ca
Show file tree
Hide file tree
Showing 8 changed files with 732 additions and 5 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

- Added the `backupGlobals`, `backupGlobalsExcludeList`, `backupStaticAttributes`, `backupStaticAttributesExcludeList` to the `WPLoader` module configuration file to provide a sweeping control over the state snapshot of test cases extending the `WPTestCase` class. Test cases overriding those properties explicitly will have their values respected.

## [4.0.11] 2023-11-24;

### Fixed
Expand Down
68 changes: 68 additions & 0 deletions docs/modules/WPLoader.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ When used in this mode, the module supports the following configuration paramete
* `WP_HTTP_BLOCK_EXTERNAL` - the `WP_HTTP_BLOCK_EXTERNAL` constant value to use when loading WordPress. If
the `wpRootFolder` path points at a configured installation, containing the `wp-config.php` file, then the value of
the constant in the configuration file will be used, else it will be randomly generated.
* `backupGlobals` - a boolean value to indicate if the global environment should be backed up before each test. Defaults to `true`. The globals' 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 global environment backup, or use a more refined approach setting the `backupGlobalsExcludeList` parameter below. Note that a test case that is explicitly setting the `backupGlobals` property will override this configuration parameter.
* `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.

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

Expand Down Expand Up @@ -153,6 +157,70 @@ modules:
theme: twentytwentythree
```
The follow example configuration prevents the backup of globals and static attributes in all the tests of the suite that are not explicitly overriding the `backupGlobals` and `backupStaticAttributes` properties:

```yaml
actor: IntegrationTester
bootstrap: _bootstrap.php
modules:
enabled:
- \Helper\Integration
- lucatume\WPBrowser\Module\WPLoader:
wpRootFolder: /var/wordpress
dbUrl: sqlite:///var/wordpress/wp-tests.sqlite
dump:
- tests/_data/products.sql
- tests/_data/users.sql
- tests/_data/orders.sql
tablePrefix: test_
domain: wordpress.test
adminEmail: admin@wordpress.test
title: 'Integration Tests'
plugins:
- hello.php
- woocommerce/woocommerce.php
- my-plugin/my-plugin.php
theme: twentytwentythree
backupGlobals: false
backupStaticAttributes: false
```

The following configuration prevents the backup of *some* globals and static attributes:

```yaml
actor: IntegrationTester
bootstrap: _bootstrap.php
modules:
enabled:
- \Helper\Integration
- lucatume\WPBrowser\Module\WPLoader:
wpRootFolder: /var/wordpress
dbUrl: sqlite:///var/wordpress/wp-tests.sqlite
dump:
- tests/_data/products.sql
- tests/_data/users.sql
- tests/_data/orders.sql
tablePrefix: test_
domain: wordpress.test
adminEmail: admin@wordpress.test
title: 'Integration Tests'
plugins:
- hello.php
- woocommerce/woocommerce.php
- my-plugin/my-plugin.php
theme: twentytwentythree
backupGlobalsExcludeList:
- my_plugin_will_explode_on_wakeup
- another_problematic_global
backupStaticAttributesExcludeList:
- MyPlugin\MyClass:
- instance
- anotherStaticAttributeThatWillExplodeOnWakeup
- AnotherPlugin\AnotherClass:
- instance
- yetAnotherStaticAttributeThatWillExplodeOnWakeup
```

### Handling a custom site structure

If you're working on a site project using a custom file structure, e.g. [Bedrock][4], you should
Expand Down
68 changes: 65 additions & 3 deletions src/Module/WPLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use lucatume\WPBrowser\Process\Loop;
use lucatume\WPBrowser\Process\ProcessException;
use lucatume\WPBrowser\Process\WorkerException;
use lucatume\WPBrowser\Utils\Arr;
use lucatume\WPBrowser\Utils\CorePHPUnit;
use lucatume\WPBrowser\Utils\Db as DbUtils;
use lucatume\WPBrowser\Utils\Filesystem as FS;
Expand Down Expand Up @@ -118,7 +119,11 @@ class WPLoader extends Module
* WP_PLUGIN_DIR?: ?string,
* WPMU_PLUGIN_DIR?: ?string,
* dump: string|string[],
* dbUrl?: string
* dbUrl?: string,
* backupGlobals?: bool,
* backupGlobalsExcludeList?: string[],
* backupStaticAttributes?: bool,
* backupStaticAttributesExcludeList?: array<string,string[]>,
* }
*/
protected array $config = [
Expand Down Expand Up @@ -150,7 +155,11 @@ class WPLoader extends Module
'WP_CONTENT_DIR' => null,
'WP_PLUGIN_DIR' => null,
'WPMU_PLUGIN_DIR' => null,
'dump' => ''
'dump' => '',
'backupGlobals' => true,
'backupGlobalsExcludeList' => [],
'backupStaticAttributes' => true,
'backupStaticAttributesExcludeList' => [],
];

private string $wpBootstrapFile;
Expand Down Expand Up @@ -220,6 +229,48 @@ protected function validateConfig(): void
}
}

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

if (isset($this->config['backupGlobalsExcludeList'])
&& !(
is_array($this->config['backupGlobalsExcludeList'])
&& Arr::containsOnly($this->config['backupGlobalsExcludeList'], 'string')
)
) {
throw new ModuleConfigException(
__CLASS__,
'The `backupGlobalsExcludeList` configuration parameter an array of strings.'
);
}

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

if (isset($this->config['backupStaticAttributesExcludeList'])
&& !(
is_array($this->config['backupStaticAttributesExcludeList'])
&& Arr::isAssociative($this->config['backupStaticAttributesExcludeList'])
&& Arr::containsOnly(
$this->config['backupStaticAttributesExcludeList'],
static fn($v) => Arr::containsOnly($v, 'string')
)
)
) {
throw new ModuleConfigException(
__CLASS__,
'The `backupStaticAttributesExcludeList` configuration parameter an array of strings.'
);
}

parent::validateConfig();
}

Expand Down Expand Up @@ -274,11 +325,22 @@ public function _initialize(): void
* tablePrefix: string,
* WP_CONTENT_DIR?: string,
* WP_PLUGIN_DIR?: string,
* WPMU_PLUGIN_DIR?: string
* WPMU_PLUGIN_DIR?: string,
* backupGlobals: bool,
* backupGlobalsExcludeList: string[],
* backupStaticAttributes: bool,
* backupStaticAttributesExcludeList: array<string,string[]>,
* } $config
*/
$config = $this->config;
try {
global $_wpTestsBackupGlobals, $_wpTestsBackupGlobalsExcludeList,
$_wpTestsBackupStaticAttributes, $_wpTestsBackupStaticAttributesExcludeList;
$_wpTestsBackupGlobals = (bool)$config['backupGlobals'];
$_wpTestsBackupGlobalsExcludeList = (array)$config['backupGlobalsExcludeList'];
$_wpTestsBackupStaticAttributes = (bool)$config['backupStaticAttributes'];
$_wpTestsBackupStaticAttributesExcludeList = (array)$config['backupStaticAttributesExcludeList'];

if (empty($config['dbHost']) && str_starts_with($config['dbName'], codecept_root_dir())) {
$dbFile = (array_reverse(explode(DIRECTORY_SEPARATOR, $config['dbName']))[0]);
$dbDir = rtrim(str_replace($dbFile, '', $config['dbName']), DIRECTORY_SEPARATOR);
Expand Down
63 changes: 62 additions & 1 deletion src/TestCase/WPTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace lucatume\WPBrowser\TestCase;

use Codeception\Test\Unit;
use lucatume\WPBrowser\Module\WPLoader;
use lucatume\WPBrowser\Module\WPQueries;
use ReflectionException;
use ReflectionMethod;
Expand Down Expand Up @@ -39,7 +40,12 @@ class WPTestCase extends Unit
// WooCommerce.
'woocommerce',
// Additional globals.
'_wp_registered_theme_features'
'_wp_registered_theme_features',
// wp-browser
'_wpTestsBackupGlobals',
'_wpTestsBackupGlobalsExcludeList',
'_wpTestsBackupStaticAttributes',
'_wpTestsBackupStaticAttributesExcludeList'
];

// Backup, and reset, static class attributes between tests.
Expand All @@ -60,6 +66,61 @@ class WPTestCase extends Unit
'Automattic\WooCommerce\RestApi\Server' => ['instance']
];

/**
* @param array<mixed> $data
*/
public function __construct(?string $name = null, array $data = [], $dataName = '')
{
global $_wpTestsBackupGlobals,
$_wpTestsBackupGlobalsExcludeList,
$_wpTestsBackupStaticAttributes,
$_wpTestsBackupStaticAttributesExcludeList;

$backupGlobalsReflectionProperty = new \ReflectionProperty($this, 'backupGlobals');
$isDefinedInThis = $backupGlobalsReflectionProperty->getDeclaringClass()->getName() !== WPTestCase::class;
if (!$isDefinedInThis && isset($_wpTestsBackupGlobals) && is_bool($_wpTestsBackupGlobals)) {
$this->backupGlobals = $_wpTestsBackupGlobals;
}

$backupGlobalsExcludeListReflectionProperty = new \ReflectionProperty($this, 'backupGlobalsExcludeList');
$isDefinedInThis = $backupGlobalsExcludeListReflectionProperty->getDeclaringClass()
->getName() !== WPTestCase::class;
if (!$isDefinedInThis
&& isset($_wpTestsBackupGlobalsExcludeList)
&& is_array($_wpTestsBackupGlobalsExcludeList)
) {
$this->backupGlobalsExcludeList = array_merge(
$this->backupGlobalsExcludeList,
$_wpTestsBackupGlobalsExcludeList
);
}

$backupStaticAttributesReflectionProperty = new \ReflectionProperty($this, 'backupStaticAttributes');
$isDefinedInThis = $backupStaticAttributesReflectionProperty->getDeclaringClass()
->getName() !== WPTestCase::class;
if (!$isDefinedInThis && isset($_wpTestsBackupStaticAttributes) && is_bool($_wpTestsBackupStaticAttributes)) {
$this->backupStaticAttributes = $_wpTestsBackupStaticAttributes;
}

$backupStaticAttributesExcludeListReflectionProperty = new \ReflectionProperty(
$this,
'backupStaticAttributesExcludeList'
);
$isDefinedInThis = $backupStaticAttributesExcludeListReflectionProperty->getDeclaringClass()
->getName() !== WPTestCase::class;
if (!$isDefinedInThis
&& isset($_wpTestsBackupStaticAttributesExcludeList)
&& is_array($_wpTestsBackupStaticAttributesExcludeList)
) {
$this->backupStaticAttributesExcludeList = array_merge_recursive(
$this->backupStaticAttributesExcludeList,
$_wpTestsBackupStaticAttributesExcludeList
);
}

parent::__construct($name, $data, $dataName);
}

/**
* @var array<string,mixed>
*/
Expand Down
60 changes: 60 additions & 0 deletions tests/_data/files/BackupControlTestCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

use lucatume\WPBrowser\TestCase\WPTestCase;

class BackupControlTestCaseStore
{
public static $staticAttribute = 'initial_value';
public static $staticAttributeTwo = 'initial_value';
public static $staticAttributeThree = 'initial_value';
public static $staticAttributeFour = 'initial_value';
}

class BackupControlTestCaseStoreTwo
{
public static $staticAttribute = 'initial_value';
public static $staticAttributeTwo = 'initial_value';
public static $staticAttributeThree = 'initial_value';
public static $staticAttributeFour = 'initial_value';
}

class BackupControlTestCase extends WPTestCase
{
/**
* Override the method to avoid issues with Codeception specific meta data.
*/
protected function _setUp()
{
$this->_before();
}

public function testBackupGlobalsIsFalse(): void
{
$this->assertFalse($this->backupGlobals);
}

public function testBackupGlobalsIsTrue(): void
{
$this->assertTrue($this->backupGlobals);
}

public function testWillUpdateTheValueOfGlobalVar(): void
{
global $_wpbrowser_test_global_var;
$_wpbrowser_test_global_var = 'updated_value';
$this->assertTrue(true); // Useless assertion to avoid the test to be marked as risky.
}

public function testWillAlterStoreStaticAttribute(): void
{
BackupControlTestCaseStore::$staticAttribute = 'updated_value';
BackupControlTestCaseStore::$staticAttributeTwo = 'updated_value';
BackupControlTestCaseStore::$staticAttributeThree = 'updated_value';
BackupControlTestCaseStore::$staticAttributeFour = 'updated_value';
BackupControlTestCaseStoreTwo::$staticAttribute = 'updated_value';
BackupControlTestCaseStoreTwo::$staticAttributeTwo = 'updated_value';
BackupControlTestCaseStoreTwo::$staticAttributeThree = 'updated_value';
BackupControlTestCaseStoreTwo::$staticAttributeFour = 'updated_value';
$this->assertTrue(true); // Useless assertion to avoid the test to be marked as risky.
}
}
33 changes: 33 additions & 0 deletions tests/_data/files/BackupControlTestCaseOverridingTestCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

use lucatume\WPBrowser\TestCase\WPTestCase;

class BackupControlTestCaseOverridingStore
{
public static $staticAttribute = 'initial_value';
}

class BackupControlTestCaseOverridingTestCase extends WPTestCase
{
protected $backupGlobals = false;
protected $backupStaticAttributes = false;

/**
* Override the method to avoid issues with Codeception specific meta data.
*/
protected function _setUp()
{
$this->_before();
}

public function testBackupGlobalsIsFalse(): void
{
$this->assertFalse($this->backupGlobals);
}

public function testWillAlterStoreStaticAttribute(): void
{
BackupControlTestCaseOverridingStore::$staticAttribute = 'updated_value';
$this->assertTrue(true); // Useless assertion to avoid the test to be marked as risky.
}
}
2 changes: 1 addition & 1 deletion tests/_support/_generated/WploaderTesterActions.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php //[STAMP] 13232c5d81f0d10f7a5236f475b9d7e6
<?php //[STAMP] 78c0fea8c98ec2d182b5db7ffedc0a1e
// phpcs:ignoreFile
namespace _generated;

Expand Down
Loading

0 comments on commit eb6b5ca

Please sign in to comment.