18547c8bed5565b2e7f02c0b8aa412c83ed9d11e
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Unit / Core / ClassLoaderTest.php
1 <?php
2 namespace TYPO3\CMS\Core\Tests\Unit\Core;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2011-2013 Andreas Wolf <andreas.wolf@ikt-werk.de>
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 *
19 * This script is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * This copyright notice MUST APPEAR in all copies of the script!
25 ***************************************************************/
26
27 /**
28 * Testcase for TYPO3\CMS\Core\Core\ClassLoader
29 *
30 * @author Andreas Wolf <andreas.wolf@ikt-werk.de>
31 */
32 class ClassLoaderTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
33
34 /**
35 * @var array Backup of typo3CacheManager
36 */
37 protected $typo3CacheManager = NULL;
38
39 /**
40 * @var array Register of temporary extensions in typo3temp
41 */
42 protected $fakedExtensions = array();
43
44 /**
45 * Fix a race condition that GeneralUtility is not available
46 * during tearDown if fiddling with the autoloader where
47 * backupGlobals is not set up again yet
48 */
49 public function setUp() {
50 $this->typo3CacheManager = $GLOBALS['typo3CacheManager'];
51 }
52
53 /**
54 * Clean up
55 * Warning: Since phpunit itself is php and we are fiddling with php
56 * autoloader code here, the tests are a bit fragile. This tearDown
57 * method ensures that all main classes are available again during
58 * tear down of a testcase.
59 * This construct will fail if the class under test is changed and
60 * not compatible anymore. Make sure to always run the whole test
61 * suite if fiddling with the autoloader unit tests to ensure that
62 * there is no fatal error thrown in other unit test classes triggered
63 * by errors in this one.
64 */
65 public function tearDown() {
66 $GLOBALS['typo3CacheManager'] = $this->typo3CacheManager;
67 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
68 \TYPO3\CMS\Core\Core\ClassLoader::registerAutoloader();
69 foreach ($this->fakedExtensions as $extension) {
70 \TYPO3\CMS\Core\Utility\GeneralUtility::rmdir(PATH_site . 'typo3temp/' . $extension, TRUE);
71 }
72 }
73
74 /**
75 * Creates a fake extension inside typo3temp/. No configuration is created,
76 * just the folder, plus the extension is registered in $TYPO3_LOADED_EXT
77 *
78 * @return string The extension key
79 */
80 protected function createFakeExtension() {
81 $extKey = strtolower(uniqid('testing'));
82 $absExtPath = PATH_site . 'typo3temp/' . $extKey . '/';
83 $relPath = 'typo3temp/' . $extKey . '/';
84 \TYPO3\CMS\Core\Utility\GeneralUtility::mkdir($absExtPath);
85 $GLOBALS['TYPO3_LOADED_EXT'][$extKey] = array(
86 'siteRelPath' => $relPath
87 );
88 $GLOBALS['TYPO3_CONF_VARS']['EXT']['extListArray'][] = $extKey;
89 $this->fakedExtensions[] = $extKey;
90 \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::clearExtensionKeyMap();
91 return $extKey;
92 }
93
94 /**
95 * @test
96 */
97 public function unregisterAndRegisterAgainDoesNotFatal() {
98 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
99 \TYPO3\CMS\Core\Core\ClassLoader::registerAutoloader();
100 // If this fatals the autoload re registering went wrong
101 \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\TimeTracker\\NullTimeTracker');
102 }
103
104 /**
105 * @test
106 */
107 public function unregisterAutoloaderSetsCacheEntryWithT3libNoTags() {
108 $mockCache = $this->getMock('TYPO3\\CMS\\Core\\Cache\\Frontend\\AbstractFrontend', array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'), array(), '', FALSE);
109 $mockCache->expects($this->once())->method('set')->with($this->anything(), $this->anything(), array());
110 $GLOBALS['typo3CacheManager'] = $this->getMock('TYPO3\\CMS\\Core\\Cache\\CacheManager', array('getCache'));
111 $GLOBALS['typo3CacheManager']->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
112 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
113 }
114
115 /**
116 * @test
117 * @expectedException \RuntimeException
118 */
119 public function autoloadFindsClassFileDefinedInExtAutoloadFile() {
120 $extKey = $this->createFakeExtension();
121 $extPath = PATH_site . 'typo3temp/' . $extKey . '/';
122 $autoloaderFile = $extPath . 'ext_autoload.php';
123 $class = strtolower('tx_{' . $extKey . '}_' . uniqid(''));
124 $file = $extPath . uniqid('') . '.php';
125 file_put_contents($file, '<?php' . LF . 'throw new \\RuntimeException(\'\', 1310203812);' . LF . '?>');
126 file_put_contents($autoloaderFile, '<?php' . LF . 'return array(\'' . $class . '\' => \'' . $file . '\');' . LF . '?>');
127 // Inject a dummy for the core_phpcode cache to force the autoloader
128 // to re calculate the registry
129 $mockCache = $this->getMock('TYPO3\\CMS\\Core\\Cache\\Frontend\\AbstractFrontend', array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'), array(), '', FALSE);
130 $GLOBALS['typo3CacheManager'] = $this->getMock('TYPO3\\CMS\\Core\\Cache\\CacheManager', array('getCache'));
131 $GLOBALS['typo3CacheManager']->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
132 // Re-initialize autoloader registry to force it to recognize the new extension
133 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
134 \TYPO3\CMS\Core\Core\ClassLoader::registerAutoloader();
135 // Expect the exception of the file to be thrown
136 \TYPO3\CMS\Core\Core\ClassLoader::autoload($class);
137 }
138
139 /**
140 * @test
141 */
142 public function unregisterAutoloaderWritesLowerCasedClassFileToCache() {
143 $extKey = $this->createFakeExtension();
144 $extPath = PATH_site . 'typo3temp/' . $extKey . '/';
145 $autoloaderFile = $extPath . 'ext_autoload.php';
146 // A case sensitive key (FooBar) in ext_autoload file
147 $class = 'tx_{' . $extKey . '}_' . uniqid('FooBar');
148 $file = $extPath . uniqid('') . '.php';
149 file_put_contents($autoloaderFile, '<?php' . LF . 'return array(\'' . $class . '\' => \'' . $file . '\');' . LF . '?>');
150 // Inject a dummy for the core_phpcode cache to force the autoloader
151 // to re calculate the registry
152 $mockCache = $this->getMock('TYPO3\\CMS\\Core\\Cache\\Frontend\\AbstractFrontend', array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'), array(), '', FALSE);
153 $GLOBALS['typo3CacheManager'] = $this->getMock('TYPO3\\CMS\\Core\\Cache\\CacheManager', array('getCache'));
154 $GLOBALS['typo3CacheManager']->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
155 // Expect that the lower case version of the class name is written to cache
156 $mockCache->expects($this->at(2))->method('set')->with($this->anything(), $this->stringContains(strtolower($class), FALSE));
157 // Re-initialize autoloader registry to force it to recognize the new extension
158 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
159 \TYPO3\CMS\Core\Core\ClassLoader::registerAutoloader();
160 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
161 }
162
163 /**
164 * @test
165 * @expectedException \RuntimeException
166 */
167 public function autoloadFindsClassFileIfExtAutoloadEntryIsCamelCased() {
168 $extKey = $this->createFakeExtension();
169 $extPath = PATH_site . 'typo3temp/' . $extKey . '/';
170 // A case sensitive key (FooBar) in ext_autoload file
171 $class = 'tx_{' . $extKey . '}_' . uniqid('FooBar');
172 $file = $extPath . uniqid('') . '.php';
173 file_put_contents($file, '<?php' . LF . 'throw new \\RuntimeException(\'\', 1336756850);' . LF . '?>');
174 $extAutoloadFile = $extPath . 'ext_autoload.php';
175 file_put_contents($extAutoloadFile, '<?php' . LF . 'return array(\'' . $class . '\' => \'' . $file . '\');' . LF . '?>');
176 // Inject cache and return false, so autoloader is forced to read ext_autoloads from extensions
177 $mockCache = $this->getMock('TYPO3\\CMS\\Core\\Cache\\Frontend\\AbstractFrontend', array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'), array(), '', FALSE);
178 $GLOBALS['typo3CacheManager'] = $this->getMock('TYPO3\\CMS\\Core\\Cache\\CacheManager', array('getCache'));
179 $GLOBALS['typo3CacheManager']->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
180 $mockCache->expects($this->any())->method('has')->will($this->returnValue(FALSE));
181 // Re-initialize autoloader registry to force it to recognize the new extension
182 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
183 \TYPO3\CMS\Core\Core\ClassLoader::registerAutoloader();
184 \TYPO3\CMS\Core\Core\ClassLoader::autoload($class);
185 }
186
187 /**
188 * @test
189 * @expectedException \RuntimeException
190 */
191 public function autoloadFindsCamelCasedClassFileIfExtAutoloadEntryIsReadLowerCasedFromCache() {
192 $extKey = $this->createFakeExtension();
193 $extPath = PATH_site . 'typo3temp/' . $extKey . '/';
194 // A case sensitive key (FooBar) in ext_autoload file
195 $class = 'tx_{' . $extKey . '}_' . uniqid('FooBar');
196 $file = $extPath . uniqid('') . '.php';
197 file_put_contents($file, '<?php' . LF . 'throw new \RuntimeException(\'\', 1336756850);' . LF . '?>');
198 // Inject cache mock and let the cache entry return the lowercased class name as key
199 $mockCache = $this->getMock('TYPO3\\CMS\\Core\\Cache\\Frontend\\AbstractFrontend', array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'), array(), '', FALSE);
200 $GLOBALS['typo3CacheManager'] = $this->getMock('TYPO3\\CMS\\Core\\Cache\\CacheManager', array('getCache'));
201 $GLOBALS['typo3CacheManager']->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
202 $mockCache->expects($this->any())->method('has')->will($this->returnValue(TRUE));
203 $mockCache->expects($this->once())->method('requireOnce')->will($this->returnValue(array(array(strtolower($class) => $file))));
204 // Re-initialize autoloader registry to force it to recognize the new extension
205 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
206 \TYPO3\CMS\Core\Core\ClassLoader::registerAutoloader();
207 \TYPO3\CMS\Core\Core\ClassLoader::autoload($class);
208 }
209
210 /**
211 * @test
212 * @expectedException \RuntimeException
213 */
214 public function autoloadFindsClassFileThatRespectsExtbaseNamingSchemeWithoutExtAutoloadFile() {
215 $extKey = $this->createFakeExtension();
216 $extPath = PATH_site . 'typo3temp/' . $extKey . '/';
217 // Create a class named Tx_Extension_Foo123_Bar456
218 // to find file extension/Classes/Foo123/Bar456.php
219 $pathSegment = 'Foo' . uniqid();
220 $fileName = 'Bar' . uniqid();
221 $class = 'Tx_' . ucfirst($extKey) . '_' . $pathSegment . '_' . $fileName;
222
223 $file = $extPath . 'Classes/' . $pathSegment . '/' . $fileName . '.php';
224 \TYPO3\CMS\Core\Utility\GeneralUtility::mkdir_deep($extPath . 'Classes/' . $pathSegment);
225 file_put_contents($file, '<?php' . LF . 'throw new \\RuntimeException(\'\', 1310203813);' . LF . '?>');
226 // Inject a dummy for the core_phpcode cache to cache
227 // the calculated cache entry to a dummy cache
228 $mockCache = $this->getMock('TYPO3\\CMS\\Core\\Cache\\Frontend\\AbstractFrontend', array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'), array(), '', FALSE);
229 $GLOBALS['typo3CacheManager'] = $this->getMock('TYPO3\\CMS\\Core\\Cache\\CacheManager', array('getCache'));
230 $GLOBALS['typo3CacheManager']->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
231 // Expect the exception of the file to be thrown
232 \TYPO3\CMS\Core\Core\ClassLoader::autoload($class);
233 }
234
235 /**
236 * @test
237 */
238 public function unregisterAutoloaderWritesClassFileThatRespectsExtbaseNamingSchemeToCacheFile() {
239 $extKey = $this->createFakeExtension();
240 $extPath = PATH_site . 'typo3temp/' . $extKey . '/';
241 $pathSegment = 'Foo' . uniqid();
242 $fileName = 'Bar' . uniqid();
243 $class = 'Tx_' . $extKey . '_' . $pathSegment . '_' . $fileName;
244 $file = $extPath . 'Classes/' . $pathSegment . '/' . $fileName . '.php';
245 \TYPO3\CMS\Core\Utility\GeneralUtility::mkdir_deep($extPath . 'Classes/' . $pathSegment);
246 file_put_contents($file, '<?php' . LF . '$foo = \'bar\';' . LF . '?>');
247 $mockCache = $this->getMock('TYPO3\\CMS\\Core\\Cache\\Frontend\\AbstractFrontend', array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'), array(), '', FALSE);
248 $GLOBALS['typo3CacheManager'] = $this->getMock('TYPO3\\CMS\\Core\\Cache\\CacheManager', array('getCache'));
249 $GLOBALS['typo3CacheManager']->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
250 // Expect that an entry to the cache is written containing the newly found class
251 $mockCache->expects($this->once())->method('set')->with($this->anything(), $this->stringContains(strtolower($class), $this->anything()));
252 \TYPO3\CMS\Core\Core\ClassLoader::autoload($class);
253 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
254 }
255
256 /**
257 * @test
258 */
259 public function unregisterAutoloaderWritesClassFileLocationOfClassRespectingExtbaseNamingSchemeToCacheFile() {
260 $extKey = $this->createFakeExtension();
261 $extPath = PATH_site . 'typo3temp/' . $extKey . '/';
262 $pathSegment = 'Foo' . uniqid();
263 $fileName = 'Bar' . uniqid();
264 $class = 'Tx_' . $extKey . '_' . $pathSegment . '_' . $fileName;
265 $file = $extPath . 'Classes/' . $pathSegment . '/' . $fileName . '.php';
266 \TYPO3\CMS\Core\Utility\GeneralUtility::mkdir_deep($extPath . 'Classes/' . $pathSegment);
267 file_put_contents($file, '<?php' . LF . '$foo = \'bar\';' . LF . '?>');
268 $mockCache = $this->getMock('TYPO3\\CMS\\Core\\Cache\\Frontend\\AbstractFrontend', array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'), array(), '', FALSE);
269 $GLOBALS['typo3CacheManager'] = $this->getMock('TYPO3\\CMS\\Core\\Cache\\CacheManager', array('getCache'));
270 $GLOBALS['typo3CacheManager']->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
271 // Expect that an entry to the cache is written containing the newly found class
272 $mockCache->expects($this->once())->method('set')->with($this->anything(), $this->stringContains(strtolower($file), $this->anything()));
273 \TYPO3\CMS\Core\Core\ClassLoader::autoload($class);
274 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
275 }
276
277 /**
278 * @test
279 * @expectedException \RuntimeException
280 */
281 public function autoloadFindsClassFileThatRespectsExtbaseNamingSchemeWithNamespace() {
282 $extKey = $this->createFakeExtension();
283 $extPath = PATH_site . 'typo3temp/' . $extKey . '/';
284 // Create a class named \Tx\Extension\Foo123\Bar456
285 // to find file extension/Classes/Foo123/Bar456.php
286 $pathSegment = 'Foo' . uniqid();
287 $fileName = 'Bar' . uniqid();
288 $namespacedClass = '\\Vendor\\' . ucfirst($extKey) . '\\' . $pathSegment . '\\' . $fileName;
289 $file = $extPath . 'Classes/' . $pathSegment . '/' . $fileName . '.php';
290 \TYPO3\CMS\Core\Utility\GeneralUtility::mkdir_deep($extPath . 'Classes/' . $pathSegment);
291 file_put_contents($file, '<?php' . LF . 'throw new \\RuntimeException(\'\', 1342800577);' . LF . '?>');
292 // Re-initialize autoloader registry to force it to recognize the new extension
293 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
294 \TYPO3\CMS\Core\Core\ClassLoader::registerAutoloader();
295 // Expect the exception of the file to be thrown
296 \TYPO3\CMS\Core\Core\ClassLoader::autoload($namespacedClass);
297 }
298
299 /**
300 * @test
301 */
302 public function unregisterAutoloaderWritesClassFileLocationOfClassRespectingExtbaseNamingSchemeWithNamespaceToCacheFile() {
303 $extKey = $this->createFakeExtension();
304 $extPath = PATH_site . 'typo3temp/' . $extKey . '/';
305 $pathSegment = 'Foo' . uniqid();
306 $fileName = 'Bar' . uniqid();
307 $namespacedClass = '\\Tx\\' . $extKey . '\\' . $pathSegment . '\\' . $fileName;
308 $file = $extPath . 'Classes/' . $pathSegment . '/' . $fileName . '.php';
309 \TYPO3\CMS\Core\Utility\GeneralUtility::mkdir_deep($extPath . 'Classes/' . $pathSegment);
310 file_put_contents($file, "<?php\n\n\$foo = 'bar';\n\n?>");
311 $mockCache = $this->getMock('TYPO3\\CMS\\Core\\Cache\\Frontend\\AbstractFrontend', array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'), array(), '', FALSE);
312 $GLOBALS['typo3CacheManager'] = $this->getMock('TYPO3\\CMS\\Core\\Cache\\CacheManager', array('getCache'));
313 $GLOBALS['typo3CacheManager']->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
314 // Expect that an entry to the cache is written containing the newly found class
315 $mockCache->expects($this->once())->method('set')->with($this->anything(), $this->stringContains(strtolower($file), $this->anything()));
316 \TYPO3\CMS\Core\Core\ClassLoader::autoload($namespacedClass);
317 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
318 }
319
320 /**
321 * @test
322 */
323 public function checkClassNamesNotExtbaseSchemePassAutoloaderUntouched() {
324 $class = '\\Symfony\\Foo\\Bar';
325 $this->assertNull(\TYPO3\CMS\Core\Core\ClassLoader::getClassPathByRegistryLookup($class));
326 }
327
328 /**
329 * @test
330 */
331 public function checkAutoloaderSetsNamespacedClassnamesInExtAutoloadAreWrittenToCache() {
332 $extKey = $this->createFakeExtension();
333 $extPath = PATH_site . 'typo3temp/' . $extKey . '/';
334 $pathSegment = 'Foo' . uniqid();
335 $fileName = 'Bar' . uniqid();
336 $autoloaderFile = $extPath . 'ext_autoload.php';
337 // A case sensitive key (FooBar) in ext_autoload file
338 $namespacedClass = '\\Tx\\' . $extKey . '\\' . $pathSegment . '\\' . $fileName;
339 $classFile = 'EXT:someExt/Classes/Foo/bar.php';
340 file_put_contents($autoloaderFile, '<?php' . LF . 'return ' . var_export(array($namespacedClass => $classFile), TRUE) . ';' . LF . '?>');
341 // Inject a dummy for the core_phpcode cache to force the autoloader
342 // to re calculate the registry
343 $mockCache = $this->getMock('TYPO3\\CMS\\Core\\Cache\\Frontend\\AbstractFrontend', array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'), array(), '', FALSE);
344 $GLOBALS['typo3CacheManager'] = $this->getMock('TYPO3\\CMS\\Core\\Cache\\CacheManager', array('getCache'));
345 $GLOBALS['typo3CacheManager']->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
346 // Expect that the lower case version of the class name is written to cache
347 $mockCache->expects($this->at(2))->method('set')->with($this->anything(), $this->stringContains(strtolower(addslashes($namespacedClass)), FALSE));
348 // Re-initialize autoloader registry to force it to recognize the new extension
349 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
350 \TYPO3\CMS\Core\Core\ClassLoader::registerAutoloader();
351 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
352 }
353 }