From: Kunal Mehta Date: Fri, 25 May 2018 05:13:22 +0000 (-0700) Subject: Split AutoloaderTest into a structure and class test X-Git-Tag: 1.34.0-rc.0~5288 X-Git-Url: https://git.cyclocoop.org/%7B%24www_url%7Dadmin/compta/banques/ajouter.php?a=commitdiff_plain;h=e298f548f69561a5df55114afc05d22b5e57a4df;p=lhc%2Fweb%2Fwiklou.git Split AutoloaderTest into a structure and class test AutoloaderTest covers the AutoLoader class, and AutoLoaderStructureTest covers the structure part of the test. Change-Id: Ic4e7bfd670e1c3413631bda31260cc1cc825b1a2 --- diff --git a/includes/utils/AutoloadGenerator.php b/includes/utils/AutoloadGenerator.php index 98d2c0e550..b437653975 100644 --- a/includes/utils/AutoloadGenerator.php +++ b/includes/utils/AutoloadGenerator.php @@ -389,7 +389,7 @@ class ClassCollector { return; } // Note: When changing class name discovery logic, - // AutoLoaderTest.php may also need to be updated. + // AutoLoaderStructureTest.php may also need to be updated. switch ( $token[0] ) { case T_NAMESPACE: case T_CLASS: diff --git a/tests/phpunit/includes/AutoLoaderTest.php b/tests/phpunit/includes/AutoLoaderTest.php new file mode 100644 index 0000000000..980fd8d40c --- /dev/null +++ b/tests/phpunit/includes/AutoLoaderTest.php @@ -0,0 +1,48 @@ +mergeMwGlobalArrayValue( 'wgAutoloadLocalClasses', [ + 'TestAutoloadedLocalClass' => + __DIR__ . '/../data/autoloader/TestAutoloadedLocalClass.php', + 'TestAutoloadedCamlClass' => + __DIR__ . '/../data/autoloader/TestAutoloadedCamlClass.php', + 'TestAutoloadedSerializedClass' => + __DIR__ . '/../data/autoloader/TestAutoloadedSerializedClass.php', + ] ); + AutoLoader::resetAutoloadLocalClassesLower(); + + $this->mergeMwGlobalArrayValue( 'wgAutoloadClasses', [ + 'TestAutoloadedClass' => __DIR__ . '/../data/autoloader/TestAutoloadedClass.php', + ] ); + } + + public function testCoreClass() { + $this->assertTrue( class_exists( 'TestAutoloadedLocalClass' ) ); + } + + public function testExtensionClass() { + $this->assertTrue( class_exists( 'TestAutoloadedClass' ) ); + } + + public function testWrongCaseClass() { + $this->setMwGlobals( 'wgAutoloadAttemptLowercase', true ); + + $this->assertTrue( class_exists( 'testautoLoadedcamlCLASS' ) ); + } + + public function testWrongCaseSerializedClass() { + $this->setMwGlobals( 'wgAutoloadAttemptLowercase', true ); + + $dummyCereal = 'O:29:"testautoloadedserializedclass":0:{}'; + $uncerealized = unserialize( $dummyCereal ); + $this->assertFalse( $uncerealized instanceof __PHP_Incomplete_Class, + "unserialize() can load classes case-insensitively." ); + } +} diff --git a/tests/phpunit/structure/AutoLoaderStructureTest.php b/tests/phpunit/structure/AutoLoaderStructureTest.php new file mode 100644 index 0000000000..4d6867f8e2 --- /dev/null +++ b/tests/phpunit/structure/AutoLoaderStructureTest.php @@ -0,0 +1,178 @@ +assertEquals( + $results['expected'], + $results['actual'] + ); + } + + public function providePSR4Completeness() { + foreach ( AutoLoader::$psr4Namespaces as $prefix => $dir ) { + foreach ( $this->recurseFiles( $dir ) as $file ) { + yield [ $prefix, $file ]; + } + } + } + + private function recurseFiles( $dir ) { + return ( new File_Iterator_Facade() )->getFilesAsArray( $dir, [ '.php' ] ); + } + + /** + * @dataProvider providePSR4Completeness + */ + public function testPSR4Completeness( $prefix, $file ) { + global $wgAutoloadLocalClasses, $wgAutoloadClasses; + $contents = file_get_contents( $file ); + list( $classesInFile, $aliasesInFile ) = self::parseFile( $contents ); + $classes = array_keys( $classesInFile ); + $this->assertCount( 1, $classes, + "Only one class per file in PSR-4 autoloaded classes ($file)" ); + + $this->assertStringStartsWith( $prefix, $classes[0] ); + $this->assertTrue( + class_exists( $classes[0] ) || interface_exists( $classes[0] ) || trait_exists( $classes[0] ), + "Class {$classes[0]} not autoloaded properly" + ); + + $otherClasses = $wgAutoloadLocalClasses + $wgAutoloadClasses; + foreach ( $aliasesInFile as $alias => $class ) { + $this->assertArrayHasKey( $alias, $otherClasses, + 'Alias must be in the classmap autoloader' + ); + } + } + + 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+ + (?P [a-zA-Z0-9_]+) + | + class_alias \s* \( \s* + ([\'"]) (?P [^\'"]+) \g{-2} \s* , \s* + ([\'"]) (?P [^\'"]+ ) \g{-2} \s* + \) \s* ; + | + class_alias \s* \( \s* + (?P [a-zA-Z0-9_]+)::class \s* , \s* + ([\'"]) (?P [^\'"]+ ) \g{-2} \s* + \) \s* ; + ) + /imx', $contents, $matches, PREG_SET_ORDER ); + + $namespaceMatch = []; + preg_match( '/ + ^ [\t ]* + namespace \s+ + ([a-zA-Z0-9_]+(\\\\[a-zA-Z0-9_]+)*) + \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; + } else { + 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']; + } + } + } + + return [ $classesInFile, $aliasesInFile ]; + } + + protected static function checkAutoLoadConf() { + global $wgAutoloadLocalClasses, $wgAutoloadClasses, $IP; + + // wgAutoloadLocalClasses has precedence, just like in includes/AutoLoader.php + $expected = $wgAutoloadLocalClasses + $wgAutoloadClasses; + $actual = []; + + $files = array_unique( $expected ); + + foreach ( $files as $class => $file ) { + // 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; + } + + if ( !file_exists( $filePath ) ) { + $actual[$class] = "[file '$filePath' does not exist]"; + continue; + } + + Wikimedia\suppressWarnings(); + $contents = file_get_contents( $filePath ); + Wikimedia\restoreWarnings(); + + if ( $contents === false ) { + $actual[$class] = "[couldn't read file '$filePath']"; + continue; + } + + list( $classesInFile, $aliasesInFile ) = self::parseFile( $contents ); + + foreach ( $classesInFile as $className => $ignore ) { + $actual[$className] = $file; + } + + // 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 ). + foreach ( $aliasesInFile as $alias => $class ) { + if ( isset( $classesInFile[$class] ) ) { + $actual[$alias] = $file; + } else { + $actual[$alias] = "[original class not in $file]"; + } + } + } + + return [ + 'expected' => $expected, + 'actual' => $actual, + ]; + } + + public function testAutoloadOrder() { + $path = realpath( __DIR__ . '/../../..' ); + $oldAutoload = file_get_contents( $path . '/autoload.php' ); + $generator = new AutoloadGenerator( $path, 'local' ); + $generator->setExcludePaths( array_values( AutoLoader::getAutoloadNamespaces() ) ); + $generator->initMediaWikiDefault(); + $newAutoload = $generator->getAutoload( 'maintenance/generateLocalAutoload.php' ); + + $this->assertEquals( $oldAutoload, $newAutoload, 'autoload.php does not match' . + ' output of generateLocalAutoload.php script.' ); + } +} diff --git a/tests/phpunit/structure/AutoLoaderTest.php b/tests/phpunit/structure/AutoLoaderTest.php deleted file mode 100644 index 46255f31ab..0000000000 --- a/tests/phpunit/structure/AutoLoaderTest.php +++ /dev/null @@ -1,218 +0,0 @@ -mergeMwGlobalArrayValue( 'wgAutoloadLocalClasses', [ - 'TestAutoloadedLocalClass' => - __DIR__ . '/../data/autoloader/TestAutoloadedLocalClass.php', - 'TestAutoloadedCamlClass' => - __DIR__ . '/../data/autoloader/TestAutoloadedCamlClass.php', - 'TestAutoloadedSerializedClass' => - __DIR__ . '/../data/autoloader/TestAutoloadedSerializedClass.php', - ] ); - AutoLoader::resetAutoloadLocalClassesLower(); - - $this->mergeMwGlobalArrayValue( 'wgAutoloadClasses', [ - 'TestAutoloadedClass' => __DIR__ . '/../data/autoloader/TestAutoloadedClass.php', - ] ); - } - - /** - * 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. - */ - public function testAutoLoadConfig() { - $results = self::checkAutoLoadConf(); - - $this->assertEquals( - $results['expected'], - $results['actual'] - ); - } - - public function providePSR4Completeness() { - foreach ( AutoLoader::$psr4Namespaces as $prefix => $dir ) { - foreach ( $this->recurseFiles( $dir ) as $file ) { - yield [ $prefix, $file ]; - } - } - } - - private function recurseFiles( $dir ) { - return ( new File_Iterator_Facade() )->getFilesAsArray( $dir, [ '.php' ] ); - } - - /** - * @coversNothing - * @dataProvider providePSR4Completeness - */ - public function testPSR4Completeness( $prefix, $file ) { - global $wgAutoloadLocalClasses, $wgAutoloadClasses; - $contents = file_get_contents( $file ); - list( $classesInFile, $aliasesInFile ) = self::parseFile( $contents ); - $classes = array_keys( $classesInFile ); - $this->assertCount( 1, $classes, - "Only one class per file in PSR-4 autoloaded classes ($file)" ); - - $this->assertStringStartsWith( $prefix, $classes[0] ); - $this->assertTrue( - class_exists( $classes[0] ) || interface_exists( $classes[0] ) || trait_exists( $classes[0] ), - "Class {$classes[0]} not autoloaded properly" - ); - - $otherClasses = $wgAutoloadLocalClasses + $wgAutoloadClasses; - foreach ( $aliasesInFile as $alias => $class ) { - $this->assertArrayHasKey( $alias, $otherClasses, - 'Alias must be in the classmap autoloader' - ); - } - } - - 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+ - (?P [a-zA-Z0-9_]+) - | - class_alias \s* \( \s* - ([\'"]) (?P [^\'"]+) \g{-2} \s* , \s* - ([\'"]) (?P [^\'"]+ ) \g{-2} \s* - \) \s* ; - | - class_alias \s* \( \s* - (?P [a-zA-Z0-9_]+)::class \s* , \s* - ([\'"]) (?P [^\'"]+ ) \g{-2} \s* - \) \s* ; - ) - /imx', $contents, $matches, PREG_SET_ORDER ); - - $namespaceMatch = []; - preg_match( '/ - ^ [\t ]* - namespace \s+ - ([a-zA-Z0-9_]+(\\\\[a-zA-Z0-9_]+)*) - \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; - } else { - 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']; - } - } - } - - return [ $classesInFile, $aliasesInFile ]; - } - - protected static function checkAutoLoadConf() { - global $wgAutoloadLocalClasses, $wgAutoloadClasses, $IP; - - // wgAutoloadLocalClasses has precedence, just like in includes/AutoLoader.php - $expected = $wgAutoloadLocalClasses + $wgAutoloadClasses; - $actual = []; - - $files = array_unique( $expected ); - - foreach ( $files as $class => $file ) { - // 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; - } - - if ( !file_exists( $filePath ) ) { - $actual[$class] = "[file '$filePath' does not exist]"; - continue; - } - - Wikimedia\suppressWarnings(); - $contents = file_get_contents( $filePath ); - Wikimedia\restoreWarnings(); - - if ( $contents === false ) { - $actual[$class] = "[couldn't read file '$filePath']"; - continue; - } - - list( $classesInFile, $aliasesInFile ) = self::parseFile( $contents ); - - foreach ( $classesInFile as $className => $ignore ) { - $actual[$className] = $file; - } - - // 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 ). - foreach ( $aliasesInFile as $alias => $class ) { - if ( isset( $classesInFile[$class] ) ) { - $actual[$alias] = $file; - } else { - $actual[$alias] = "[original class not in $file]"; - } - } - } - - return [ - 'expected' => $expected, - 'actual' => $actual, - ]; - } - - function testCoreClass() { - $this->assertTrue( class_exists( 'TestAutoloadedLocalClass' ) ); - } - - function testExtensionClass() { - $this->assertTrue( class_exists( 'TestAutoloadedClass' ) ); - } - - function testWrongCaseClass() { - $this->setMwGlobals( 'wgAutoloadAttemptLowercase', true ); - - $this->assertTrue( class_exists( 'testautoLoadedcamlCLASS' ) ); - } - - function testWrongCaseSerializedClass() { - $this->setMwGlobals( 'wgAutoloadAttemptLowercase', true ); - - $dummyCereal = 'O:29:"testautoloadedserializedclass":0:{}'; - $uncerealized = unserialize( $dummyCereal ); - $this->assertFalse( $uncerealized instanceof __PHP_Incomplete_Class, - "unserialize() can load classes case-insensitively." ); - } - - function testAutoloadOrder() { - $path = realpath( __DIR__ . '/../../..' ); - $oldAutoload = file_get_contents( $path . '/autoload.php' ); - $generator = new AutoloadGenerator( $path, 'local' ); - $generator->setExcludePaths( array_values( AutoLoader::getAutoloadNamespaces() ) ); - $generator->initMediaWikiDefault(); - $newAutoload = $generator->getAutoload( 'maintenance/generateLocalAutoload.php' ); - - $this->assertEquals( $oldAutoload, $newAutoload, 'autoload.php does not match' . - ' output of generateLocalAutoload.php script.' ); - } -}