2012-11-07 01:27:30 +00:00
|
|
|
<?php
|
|
|
|
|
|
2013-11-07 16:50:23 +00:00
|
|
|
class AutoLoaderTest extends MediaWikiTestCase {
|
2013-10-27 05:59:01 +00:00
|
|
|
protected function setUp() {
|
|
|
|
|
parent::setUp();
|
|
|
|
|
|
|
|
|
|
// Fancy dance to trigger a rebuild of AutoLoader::$autoloadLocalClassesLower
|
2016-02-17 09:09:32 +00:00
|
|
|
$this->mergeMwGlobalArrayValue( 'wgAutoloadLocalClasses', [
|
2014-10-15 16:05:57 +00:00
|
|
|
'TestAutoloadedLocalClass' =>
|
|
|
|
|
__DIR__ . '/../data/autoloader/TestAutoloadedLocalClass.php',
|
|
|
|
|
'TestAutoloadedCamlClass' =>
|
|
|
|
|
__DIR__ . '/../data/autoloader/TestAutoloadedCamlClass.php',
|
2014-04-24 17:52:51 +00:00
|
|
|
'TestAutoloadedSerializedClass' =>
|
|
|
|
|
__DIR__ . '/../data/autoloader/TestAutoloadedSerializedClass.php',
|
2016-02-17 09:09:32 +00:00
|
|
|
] );
|
2013-11-07 16:50:23 +00:00
|
|
|
AutoLoader::resetAutoloadLocalClassesLower();
|
2013-10-27 05:59:01 +00:00
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
$this->mergeMwGlobalArrayValue( 'wgAutoloadClasses', [
|
2013-10-27 05:59:01 +00:00
|
|
|
'TestAutoloadedClass' => __DIR__ . '/../data/autoloader/TestAutoloadedClass.php',
|
2016-02-17 09:09:32 +00:00
|
|
|
] );
|
2013-10-27 05:59:01 +00:00
|
|
|
}
|
|
|
|
|
|
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']
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
2014-01-11 21:48:30 +00:00
|
|
|
$files = array_unique( $expected );
|
2012-11-07 01:27:30 +00:00
|
|
|
|
2015-02-13 19:31:24 +00:00
|
|
|
foreach ( $files 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;
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-10 18:29:05 +00:00
|
|
|
MediaWiki\suppressWarnings();
|
2014-01-23 20:49:39 +00:00
|
|
|
$contents = file_get_contents( $filePath );
|
2015-06-10 18:29:05 +00:00
|
|
|
MediaWiki\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
|
|
|
|
|
|
|
|
// We could use token_get_all() here, but this is faster
|
2017-02-18 03:11:33 +00:00
|
|
|
// Note: Keep in sync with ClassCollector
|
2016-02-17 09:09:32 +00:00
|
|
|
$matches = [];
|
2014-01-23 20:49:39 +00:00
|
|
|
preg_match_all( '/
|
|
|
|
|
^ [\t ]* (?:
|
2016-02-11 20:58:33 +00:00
|
|
|
(?:final\s+)? (?:abstract\s+)? (?:class|interface|trait) \s+
|
2014-01-23 20:49:39 +00:00
|
|
|
(?P<class> [a-zA-Z0-9_]+)
|
|
|
|
|
|
|
|
|
|
|
class_alias \s* \( \s*
|
|
|
|
|
([\'"]) (?P<original> [^\'"]+) \g{-2} \s* , \s*
|
|
|
|
|
([\'"]) (?P<alias> [^\'"]+ ) \g{-2} \s*
|
|
|
|
|
\) \s* ;
|
2017-02-18 03:11:33 +00:00
|
|
|
|
|
|
|
|
|
class_alias \s* \( \s*
|
|
|
|
|
(?P<originalStatic> [a-zA-Z0-9_]+)::class \s* , \s*
|
|
|
|
|
([\'"]) (?P<aliasString> [^\'"]+ ) \g{-2} \s*
|
|
|
|
|
\) \s* ;
|
2014-01-23 20:49:39 +00:00
|
|
|
)
|
|
|
|
|
/imx', $contents, $matches, PREG_SET_ORDER );
|
|
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
$namespaceMatch = [];
|
2014-07-16 00:49:18 +00:00
|
|
|
preg_match( '/
|
|
|
|
|
^ [\t ]*
|
|
|
|
|
namespace \s+
|
|
|
|
|
([a-zA-Z0-9_]+(\\\\[a-zA-Z0-9_]+)*)
|
|
|
|
|
\s* ;
|
|
|
|
|
/imx', $contents, $namespaceMatch );
|
|
|
|
|
$fileNamespace = $namespaceMatch ? $namespaceMatch[1] . '\\' : '';
|
|
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
$classesInFile = [];
|
|
|
|
|
$aliasesInFile = [];
|
2014-01-23 20:49:39 +00:00
|
|
|
|
|
|
|
|
foreach ( $matches as $match ) {
|
|
|
|
|
if ( !empty( $match['class'] ) ) {
|
2017-02-18 03:11:33 +00:00
|
|
|
// 'class Foo {}'
|
2014-07-16 00:49:18 +00:00
|
|
|
$class = $fileNamespace . $match['class'];
|
|
|
|
|
$actual[$class] = $file;
|
|
|
|
|
$classesInFile[$class] = true;
|
2014-01-23 20:49:39 +00:00
|
|
|
} else {
|
2017-02-18 03:11:33 +00:00
|
|
|
if ( !empty( $match['original'] ) ) {
|
|
|
|
|
// 'class_alias( "Foo", "Bar" );'
|
|
|
|
|
$aliasesInFile[$match['alias']] = $match['original'];
|
|
|
|
|
} else {
|
|
|
|
|
// 'class_alias( Foo::class, "Bar" );'
|
|
|
|
|
$aliasesInFile[$match['aliasString']] = $fileNamespace . $match['originalStatic'];
|
|
|
|
|
}
|
2014-01-23 20:49:39 +00:00
|
|
|
}
|
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
|
|
|
|
|
|
|
|
function testCoreClass() {
|
|
|
|
|
$this->assertTrue( class_exists( 'TestAutoloadedLocalClass' ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function testExtensionClass() {
|
|
|
|
|
$this->assertTrue( class_exists( 'TestAutoloadedClass' ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function testWrongCaseClass() {
|
2015-03-21 08:41:13 +00:00
|
|
|
$this->setMwGlobals( 'wgAutoloadAttemptLowercase', true );
|
|
|
|
|
|
2013-10-27 05:59:01 +00:00
|
|
|
$this->assertTrue( class_exists( 'testautoLoadedcamlCLASS' ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function testWrongCaseSerializedClass() {
|
2015-03-21 08:41:13 +00:00
|
|
|
$this->setMwGlobals( 'wgAutoloadAttemptLowercase', true );
|
|
|
|
|
|
2013-10-27 05:59:01 +00:00
|
|
|
$dummyCereal = 'O:29:"testautoloadedserializedclass":0:{}';
|
|
|
|
|
$uncerealized = unserialize( $dummyCereal );
|
|
|
|
|
$this->assertFalse( $uncerealized instanceof __PHP_Incomplete_Class,
|
2013-11-19 18:03:54 +00:00
|
|
|
"unserialize() can load classes case-insensitively." );
|
2013-10-27 05:59:01 +00:00
|
|
|
}
|
2016-01-20 18:27:47 +00:00
|
|
|
|
|
|
|
|
function testAutoloadOrder() {
|
|
|
|
|
$path = realpath( __DIR__ . '/../../..' );
|
|
|
|
|
$oldAutoload = file_get_contents( $path . '/autoload.php' );
|
|
|
|
|
$generator = new AutoloadGenerator( $path, 'local' );
|
|
|
|
|
$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
|
|
|
}
|