phpunit: Determine what extensions to load in unit tests via config
When running unit tests, the bootstrap would previously load all extensions and skins in the filesystem. This was OK for an initial implementation, but is not acceptable if we want to eventually do that for all PHPUnit entry points (once we'll have a single config and bootstrap). Instead, it's desirable to only load the extensions specified in LocalSettings.php. The problem is that it's pretty much impossible to load LocalSettings.php without also loading the rest of MediaWiki, with all the side effects this might have. This patch introduces a helper script that loads all the config, then prints what extensions and skins were loaded. The bootstrap file runs this script via proc_open and then reads the list of extensions to load. Because the script is run in a separate process, any side effects only affect the spawned process, not the one where PHPUnit is running. Currently, there doesn't seem to be a better way to obtain the list of extensions loaded in LocalSettings.php without all the other side effects. YAML settings (https://www.mediawiki.org/wiki/Manual:YAML_settings_file_format) would probably help, but that's very far from becoming the only supported config format (if it will ever be). Also add two TestSuite implementations to replace the '*' wildcard in the extensions:unit and skins:unit suites. These use the same list of loaded extensions to determine where to look for tests. And last but not least: my most sincere apologies to you if the hack you're seeing here has ruined your day. If you think a better approach exists, please tell me and I'll be so relieved! Bug: T227900 Change-Id: Ib578644b8a4c0b64dca607afb9eb8204ca7fc660
This commit is contained in:
parent
1c9a6c1cfe
commit
a053d106bf
5 changed files with 129 additions and 16 deletions
|
|
@ -27,10 +27,10 @@
|
|||
<directory>tests/phpunit/unit</directory>
|
||||
</testsuite>
|
||||
<testsuite name="extensions:unit">
|
||||
<directory>extensions/*/tests/phpunit/unit</directory>
|
||||
<file>tests/phpunit/suites/ExtensionsUnitTestSuite.php</file>
|
||||
</testsuite>
|
||||
<testsuite name="skins:unit">
|
||||
<directory>skins/*/tests/phpunit/unit</directory>
|
||||
<file>tests/phpunit/suites/SkinsUnitTestSuite.php</file>
|
||||
</testsuite>
|
||||
<testsuite name="includes">
|
||||
<directory>tests/phpunit/includes</directory>
|
||||
|
|
|
|||
|
|
@ -48,24 +48,40 @@ TestSetup::requireOnceInGlobalScope( MW_INSTALL_PATH . "/includes/DevelopmentSet
|
|||
|
||||
TestSetup::applyInitialConfig();
|
||||
|
||||
// Since we do not load settings, expect to find extensions and skins
|
||||
// in their respective default locations.
|
||||
$GLOBALS['wgExtensionDirectory'] = MW_INSTALL_PATH . "/extensions";
|
||||
$GLOBALS['wgStyleDirectory'] = MW_INSTALL_PATH . "/skins";
|
||||
// Shell out to another script that will give us a list of loaded extensions and skins. We need to do that in another
|
||||
// process, not in this one, because loading setting files may have non-trivial side effects that could be hard
|
||||
// to undo. This sucks, but there doesn't seem to be a way to get a list of extensions and skins without loading
|
||||
// all of MediaWiki, which we don't want to do for unit tests.
|
||||
// phpcs:ignore MediaWiki.Usage.ForbiddenFunctions.proc_open
|
||||
$process = proc_open(
|
||||
__DIR__ . '/getPHPUnitExtensionsAndSkins.php',
|
||||
[
|
||||
0 => [ 'pipe' ,'r' ],
|
||||
1 => [ 'pipe', 'w' ],
|
||||
2 => [ 'pipe', 'w' ]
|
||||
],
|
||||
$pipes
|
||||
);
|
||||
|
||||
// Populate classes and namespaces from extensions and skins present in filesystem.
|
||||
$directoryToJsonMap = [
|
||||
$GLOBALS['wgExtensionDirectory'] => 'extension*.json',
|
||||
$GLOBALS['wgStyleDirectory'] => 'skin*.json'
|
||||
];
|
||||
$pathsToJsonFilesStr = stream_get_contents( $pipes[1] );
|
||||
fclose( $pipes[1] );
|
||||
$cmdErr = stream_get_contents( $pipes[2] );
|
||||
fclose( $pipes[2] );
|
||||
$exitCode = proc_close( $process );
|
||||
if ( $exitCode !== 0 ) {
|
||||
echo "Cannot load list of extensions and skins. Output:\n$cmdErr\n";
|
||||
exit( 1 );
|
||||
}
|
||||
|
||||
$pathsToJsonFiles = explode( "\n", $pathsToJsonFilesStr );
|
||||
|
||||
/** @internal For use in ExtensionsUnitTestSuite and SkinsUnitTestSuite only */
|
||||
define( 'MW_PHPUNIT_EXTENSIONS_PATHS', array_map( 'dirname', $pathsToJsonFiles ) );
|
||||
|
||||
$extensionProcessor = new ExtensionProcessor();
|
||||
|
||||
foreach ( $directoryToJsonMap as $directory => $jsonFilePattern ) {
|
||||
foreach ( new GlobIterator( $directory . '/*/' . $jsonFilePattern ) as $iterator ) {
|
||||
$jsonPath = $iterator->getPathname();
|
||||
$extensionProcessor->extractInfoFromFile( $jsonPath );
|
||||
}
|
||||
foreach ( $pathsToJsonFiles as $filePath ) {
|
||||
$extensionProcessor->extractInfoFromFile( $filePath );
|
||||
}
|
||||
|
||||
$autoload = $extensionProcessor->getExtractedAutoloadInfo( true );
|
||||
|
|
|
|||
25
tests/phpunit/getPHPUnitExtensionsAndSkins.php
Executable file
25
tests/phpunit/getPHPUnitExtensionsAndSkins.php
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* WARNING: Hic sunt dracones!
|
||||
*
|
||||
* This script is used in the PHPUnit bootstrap to get a list of extensions and skins to autoload,
|
||||
* without having the bootstrap file itself load any settings. It is a HUGE but unavoidable hack,
|
||||
* if we want to avoid loading settings in unit tests, and at the same time only load the extensions
|
||||
* enabled in LocalSettings.php without triggering any other side effects of Setup.php and the other
|
||||
* files it includes.
|
||||
* One day this may become unnecessary if we enforce YAML settings with a static list of extensions
|
||||
* and skins (https://www.mediawiki.org/wiki/Manual:YAML_settings_file_format).
|
||||
* The script was introduced for T227900, the idea being to have a single config file that can be used
|
||||
* with both unit and integration tests.
|
||||
* @internal This script should only be invoked by bootstrap.php, as part of the PHPUnit bootstrap process
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/bootstrap.common.php';
|
||||
|
||||
TestSetup::loadSettingsFiles();
|
||||
|
||||
$extensionsAndSkins = ExtensionRegistry::getInstance()->getQueue();
|
||||
|
||||
echo implode( "\n", array_keys( $extensionsAndSkins ) );
|
||||
36
tests/phpunit/suites/ExtensionsUnitTestSuite.php
Normal file
36
tests/phpunit/suites/ExtensionsUnitTestSuite.php
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
use PHPUnit\Framework\TestSuite;
|
||||
use SebastianBergmann\FileIterator\Facade;
|
||||
|
||||
/**
|
||||
* Test suite that runs extensions unit tests (the `extensions:unit` suite).
|
||||
*/
|
||||
class ExtensionsUnitTestSuite extends TestSuite {
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
if ( !defined( 'MW_PHPUNIT_EXTENSIONS_PATHS' ) ) {
|
||||
throw new RuntimeException( 'The PHPUnit bootstrap was not loaded' );
|
||||
}
|
||||
$paths = [];
|
||||
foreach ( MW_PHPUNIT_EXTENSIONS_PATHS as $path ) {
|
||||
// Note that we don't load settings, so we expect to find extensions in their
|
||||
// default location
|
||||
// Standardize directory separators for Windows compatibility.
|
||||
if ( str_contains( strtr( $path, '\\', '/' ), '/extensions/' ) ) {
|
||||
$paths[] = "$path/tests/phpunit/unit";
|
||||
}
|
||||
}
|
||||
foreach ( array_unique( $paths ) as $path ) {
|
||||
$suffixes = [ 'Test.php' ];
|
||||
$fileIterator = new Facade();
|
||||
$matchingFiles = $fileIterator->getFilesAsArray( $path, $suffixes );
|
||||
$this->addTestFiles( $matchingFiles );
|
||||
}
|
||||
}
|
||||
|
||||
public static function suite() {
|
||||
return new self;
|
||||
}
|
||||
}
|
||||
36
tests/phpunit/suites/SkinsUnitTestSuite.php
Normal file
36
tests/phpunit/suites/SkinsUnitTestSuite.php
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
use PHPUnit\Framework\TestSuite;
|
||||
use SebastianBergmann\FileIterator\Facade;
|
||||
|
||||
/**
|
||||
* Test suite that runs skins unit tests (the `skins:unit` suite).
|
||||
*/
|
||||
class SkinsUnitTestSuite extends TestSuite {
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
if ( !defined( 'MW_PHPUNIT_EXTENSIONS_PATHS' ) ) {
|
||||
throw new RuntimeException( 'The PHPUnit bootstrap was not loaded' );
|
||||
}
|
||||
$paths = [];
|
||||
foreach ( MW_PHPUNIT_EXTENSIONS_PATHS as $path ) {
|
||||
// Note that we don't load settings, so we expect to find skins in their
|
||||
// default location
|
||||
// Standardize directory separators for Windows compatibility.
|
||||
if ( str_contains( strtr( $path, '\\', '/' ), '/skins/' ) ) {
|
||||
$paths[] = "$path/tests/phpunit/unit";
|
||||
}
|
||||
}
|
||||
foreach ( array_unique( $paths ) as $path ) {
|
||||
$suffixes = [ 'Test.php' ];
|
||||
$fileIterator = new Facade();
|
||||
$matchingFiles = $fileIterator->getFilesAsArray( $path, $suffixes );
|
||||
$this->addTestFiles( $matchingFiles );
|
||||
}
|
||||
}
|
||||
|
||||
public static function suite() {
|
||||
return new self;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue