2012-11-07 01:27:30 +00:00
|
|
|
<?php
|
|
|
|
|
|
2019-06-13 23:00:08 +00:00
|
|
|
class AutoLoaderStructureTest extends MediaWikiTestCase {
|
2013-07-17 23:55:36 +00:00
|
|
|
/**
|
|
|
|
|
* Assert that there were no classes loaded that are not registered with the AutoLoader.
|
|
|
|
|
*
|
|
|
|
|
* For example foo.php having class Foo and class Bar but only registering Foo.
|
|
|
|
|
* This is important because we should not be relying on Foo being used before Bar.
|
|
|
|
|
*/
|
2012-11-07 01:27:30 +00:00
|
|
|
public function testAutoLoadConfig() {
|
|
|
|
|
$results = self::checkAutoLoadConf();
|
|
|
|
|
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
$results['expected'],
|
|
|
|
|
$results['actual']
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-25 01:55:47 +00:00
|
|
|
private static function parseFile( $contents ) {
|
|
|
|
|
// We could use token_get_all() here, but this is faster
|
|
|
|
|
// Note: Keep in sync with ClassCollector
|
|
|
|
|
$matches = [];
|
|
|
|
|
preg_match_all( '/
|
|
|
|
|
^ [\t ]* (?:
|
|
|
|
|
(?:final\s+)? (?:abstract\s+)? (?:class|interface|trait) \s+
|
2018-11-07 17:15:51 +00:00
|
|
|
(?P<class> \w+)
|
2018-05-25 01:55:47 +00:00
|
|
|
|
|
|
|
|
|
class_alias \s* \( \s*
|
|
|
|
|
([\'"]) (?P<original> [^\'"]+) \g{-2} \s* , \s*
|
|
|
|
|
([\'"]) (?P<alias> [^\'"]+ ) \g{-2} \s*
|
|
|
|
|
\) \s* ;
|
|
|
|
|
|
|
|
|
|
|
class_alias \s* \( \s*
|
2018-11-07 17:15:51 +00:00
|
|
|
(?P<originalStatic> [\w\\\\]+)::class \s* , \s*
|
2018-05-25 01:55:47 +00:00
|
|
|
([\'"]) (?P<aliasString> [^\'"]+ ) \g{-2} \s*
|
|
|
|
|
\) \s* ;
|
|
|
|
|
)
|
|
|
|
|
/imx', $contents, $matches, PREG_SET_ORDER );
|
|
|
|
|
|
|
|
|
|
$namespaceMatch = [];
|
|
|
|
|
preg_match( '/
|
|
|
|
|
^ [\t ]*
|
|
|
|
|
namespace \s+
|
2018-11-07 17:15:51 +00:00
|
|
|
(\w+(\\\\\w+)*)
|
2018-05-25 01:55:47 +00:00
|
|
|
\s* ;
|
|
|
|
|
/imx', $contents, $namespaceMatch );
|
|
|
|
|
$fileNamespace = $namespaceMatch ? $namespaceMatch[1] . '\\' : '';
|
|
|
|
|
|
|
|
|
|
$classesInFile = [];
|
|
|
|
|
$aliasesInFile = [];
|
|
|
|
|
|
|
|
|
|
foreach ( $matches as $match ) {
|
|
|
|
|
if ( !empty( $match['class'] ) ) {
|
|
|
|
|
// 'class Foo {}'
|
|
|
|
|
$class = $fileNamespace . $match['class'];
|
|
|
|
|
$classesInFile[$class] = true;
|
2019-03-29 20:12:24 +00:00
|
|
|
} elseif ( !empty( $match['original'] ) ) {
|
|
|
|
|
// 'class_alias( "Foo", "Bar" );'
|
2020-02-21 00:01:43 +00:00
|
|
|
$aliasesInFile[self::removeSlashes( $match['alias'] )] = $match['original'];
|
2018-05-25 01:55:47 +00:00
|
|
|
} else {
|
2019-03-29 20:12:24 +00:00
|
|
|
// 'class_alias( Foo::class, "Bar" );'
|
2020-02-21 00:01:43 +00:00
|
|
|
$aliasesInFile[self::removeSlashes( $match['aliasString'] )] =
|
|
|
|
|
$fileNamespace . $match['originalStatic'];
|
2018-05-25 01:55:47 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [ $classesInFile, $aliasesInFile ];
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-21 00:01:43 +00:00
|
|
|
private static function removeSlashes( $str ) {
|
|
|
|
|
return str_replace( '\\\\', '\\', $str );
|
|
|
|
|
}
|
|
|
|
|
|
2012-11-07 01:27:30 +00:00
|
|
|
protected static function checkAutoLoadConf() {
|
|
|
|
|
global $wgAutoloadLocalClasses, $wgAutoloadClasses, $IP;
|
|
|
|
|
|
|
|
|
|
// wgAutoloadLocalClasses has precedence, just like in includes/AutoLoader.php
|
|
|
|
|
$expected = $wgAutoloadLocalClasses + $wgAutoloadClasses;
|
2016-02-17 09:09:32 +00:00
|
|
|
$actual = [];
|
2012-11-07 01:27:30 +00:00
|
|
|
|
2018-09-20 17:44:35 +00:00
|
|
|
$psr4Namespaces = [];
|
|
|
|
|
foreach ( AutoLoader::getAutoloadNamespaces() as $ns => $path ) {
|
|
|
|
|
$psr4Namespaces[rtrim( $ns, '\\' ) . '\\'] = rtrim( $path, '/' );
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-07 17:15:51 +00:00
|
|
|
foreach ( $expected as $class => $file ) {
|
2012-11-07 01:27:30 +00:00
|
|
|
// Only prefix $IP if it doesn't have it already.
|
|
|
|
|
// Generally local classes don't have it, and those from extensions and test suites do.
|
|
|
|
|
if ( substr( $file, 0, 1 ) != '/' && substr( $file, 1, 1 ) != ':' ) {
|
|
|
|
|
$filePath = "$IP/$file";
|
|
|
|
|
} else {
|
|
|
|
|
$filePath = $file;
|
|
|
|
|
}
|
2014-01-23 20:49:39 +00:00
|
|
|
|
2015-02-13 19:31:24 +00:00
|
|
|
if ( !file_exists( $filePath ) ) {
|
|
|
|
|
$actual[$class] = "[file '$filePath' does not exist]";
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-10 07:52:26 +00:00
|
|
|
Wikimedia\suppressWarnings();
|
2014-01-23 20:49:39 +00:00
|
|
|
$contents = file_get_contents( $filePath );
|
2018-02-10 07:52:26 +00:00
|
|
|
Wikimedia\restoreWarnings();
|
2015-02-13 19:31:24 +00:00
|
|
|
|
|
|
|
|
if ( $contents === false ) {
|
|
|
|
|
$actual[$class] = "[couldn't read file '$filePath']";
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2014-01-23 20:49:39 +00:00
|
|
|
|
2018-05-25 01:55:47 +00:00
|
|
|
list( $classesInFile, $aliasesInFile ) = self::parseFile( $contents );
|
2014-01-23 20:49:39 +00:00
|
|
|
|
2018-05-25 01:55:47 +00:00
|
|
|
foreach ( $classesInFile as $className => $ignore ) {
|
2018-09-20 17:44:35 +00:00
|
|
|
// Skip if it's a PSR4 class
|
|
|
|
|
$parts = explode( '\\', $className );
|
|
|
|
|
for ( $i = count( $parts ) - 1; $i > 0; $i-- ) {
|
|
|
|
|
$ns = implode( '\\', array_slice( $parts, 0, $i ) ) . '\\';
|
|
|
|
|
if ( isset( $psr4Namespaces[$ns] ) ) {
|
|
|
|
|
$expectedPath = $psr4Namespaces[$ns] . '/'
|
|
|
|
|
. implode( '/', array_slice( $parts, $i ) )
|
|
|
|
|
. '.php';
|
|
|
|
|
if ( $filePath === $expectedPath ) {
|
|
|
|
|
continue 2;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Nope, add it.
|
2018-05-25 01:55:47 +00:00
|
|
|
$actual[$className] = $file;
|
2012-11-07 01:27:30 +00:00
|
|
|
}
|
2014-01-23 20:49:39 +00:00
|
|
|
|
|
|
|
|
// Only accept aliases for classes in the same file, because for correct
|
|
|
|
|
// behavior, all aliases for a class must be set up when the class is loaded
|
|
|
|
|
// (see <https://bugs.php.net/bug.php?id=61422>).
|
|
|
|
|
foreach ( $aliasesInFile as $alias => $class ) {
|
|
|
|
|
if ( isset( $classesInFile[$class] ) ) {
|
|
|
|
|
$actual[$alias] = $file;
|
|
|
|
|
} else {
|
|
|
|
|
$actual[$alias] = "[original class not in $file]";
|
|
|
|
|
}
|
2012-11-07 01:27:30 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
return [
|
2012-11-07 01:27:30 +00:00
|
|
|
'expected' => $expected,
|
|
|
|
|
'actual' => $actual,
|
2016-02-17 09:09:32 +00:00
|
|
|
];
|
2012-11-07 01:27:30 +00:00
|
|
|
}
|
2013-10-27 05:59:01 +00:00
|
|
|
|
2018-05-25 05:13:22 +00:00
|
|
|
public function testAutoloadOrder() {
|
2019-06-18 17:26:06 +00:00
|
|
|
$path = __DIR__ . '/../../..';
|
2016-01-20 18:27:47 +00:00
|
|
|
$oldAutoload = file_get_contents( $path . '/autoload.php' );
|
|
|
|
|
$generator = new AutoloadGenerator( $path, 'local' );
|
2018-09-20 17:44:35 +00:00
|
|
|
$generator->setPsr4Namespaces( AutoLoader::getAutoloadNamespaces() );
|
2016-01-20 18:27:47 +00:00
|
|
|
$generator->initMediaWikiDefault();
|
|
|
|
|
$newAutoload = $generator->getAutoload( 'maintenance/generateLocalAutoload.php' );
|
|
|
|
|
|
|
|
|
|
$this->assertEquals( $oldAutoload, $newAutoload, 'autoload.php does not match' .
|
|
|
|
|
' output of generateLocalAutoload.php script.' );
|
|
|
|
|
}
|
2013-10-27 05:59:01 +00:00
|
|
|
}
|