0219f5335086990e8203c83d0ee8a94907733348
[yaffs-website] / vendor / symfony / debug / Tests / DebugClassLoaderTest.php
1 <?php
2
3 /*
4  * This file is part of the Symfony package.
5  *
6  * (c) Fabien Potencier <fabien@symfony.com>
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 namespace Symfony\Component\Debug\Tests;
13
14 use PHPUnit\Framework\TestCase;
15 use Symfony\Component\Debug\DebugClassLoader;
16 use Symfony\Component\Debug\ErrorHandler;
17
18 class DebugClassLoaderTest extends TestCase
19 {
20     /**
21      * @var int Error reporting level before running tests
22      */
23     private $errorReporting;
24
25     private $loader;
26
27     protected function setUp()
28     {
29         $this->errorReporting = error_reporting(E_ALL);
30         $this->loader = new ClassLoader();
31         spl_autoload_register(array($this->loader, 'loadClass'), true, true);
32         DebugClassLoader::enable();
33     }
34
35     protected function tearDown()
36     {
37         DebugClassLoader::disable();
38         spl_autoload_unregister(array($this->loader, 'loadClass'));
39         error_reporting($this->errorReporting);
40     }
41
42     public function testIdempotence()
43     {
44         DebugClassLoader::enable();
45
46         $functions = spl_autoload_functions();
47         foreach ($functions as $function) {
48             if (is_array($function) && $function[0] instanceof DebugClassLoader) {
49                 $reflClass = new \ReflectionClass($function[0]);
50                 $reflProp = $reflClass->getProperty('classLoader');
51                 $reflProp->setAccessible(true);
52
53                 $this->assertNotInstanceOf('Symfony\Component\Debug\DebugClassLoader', $reflProp->getValue($function[0]));
54
55                 return;
56             }
57         }
58
59         $this->fail('DebugClassLoader did not register');
60     }
61
62     /**
63      * @expectedException \Exception
64      * @expectedExceptionMessage boo
65      */
66     public function testThrowingClass()
67     {
68         try {
69             class_exists(__NAMESPACE__.'\Fixtures\Throwing');
70             $this->fail('Exception expected');
71         } catch (\Exception $e) {
72             $this->assertSame('boo', $e->getMessage());
73         }
74
75         // the second call also should throw
76         class_exists(__NAMESPACE__.'\Fixtures\Throwing');
77     }
78
79     public function testUnsilencing()
80     {
81         if (\PHP_VERSION_ID >= 70000) {
82             $this->markTestSkipped('PHP7 throws exceptions, unsilencing is not required anymore.');
83         }
84         if (defined('HHVM_VERSION')) {
85             $this->markTestSkipped('HHVM is not handled in this test case.');
86         }
87
88         ob_start();
89
90         $this->iniSet('log_errors', 0);
91         $this->iniSet('display_errors', 1);
92
93         // See below: this will fail with parse error
94         // but this should not be @-silenced.
95         @class_exists(__NAMESPACE__.'\TestingUnsilencing', true);
96
97         $output = ob_get_clean();
98
99         $this->assertStringMatchesFormat('%aParse error%a', $output);
100     }
101
102     public function testStacking()
103     {
104         // the ContextErrorException must not be loaded to test the workaround
105         // for https://bugs.php.net/65322.
106         if (class_exists('Symfony\Component\Debug\Exception\ContextErrorException', false)) {
107             $this->markTestSkipped('The ContextErrorException class is already loaded.');
108         }
109         if (defined('HHVM_VERSION')) {
110             $this->markTestSkipped('HHVM is not handled in this test case.');
111         }
112
113         ErrorHandler::register();
114
115         try {
116             // Trigger autoloading + E_STRICT at compile time
117             // which in turn triggers $errorHandler->handle()
118             // that again triggers autoloading for ContextErrorException.
119             // Error stacking works around the bug above and everything is fine.
120
121             eval('
122                 namespace '.__NAMESPACE__.';
123                 class ChildTestingStacking extends TestingStacking { function foo($bar) {} }
124             ');
125             $this->fail('ContextErrorException expected');
126         } catch (\ErrorException $exception) {
127             // if an exception is thrown, the test passed
128             $this->assertStringStartsWith(__FILE__, $exception->getFile());
129             if (\PHP_VERSION_ID < 70000) {
130                 $this->assertRegExp('/^Runtime Notice: Declaration/', $exception->getMessage());
131                 $this->assertEquals(E_STRICT, $exception->getSeverity());
132             } else {
133                 $this->assertRegExp('/^Warning: Declaration/', $exception->getMessage());
134                 $this->assertEquals(E_WARNING, $exception->getSeverity());
135             }
136         } finally {
137             restore_error_handler();
138             restore_exception_handler();
139         }
140     }
141
142     /**
143      * @expectedException \RuntimeException
144      * @expectedExceptionMessage Case mismatch between loaded and declared class names
145      */
146     public function testNameCaseMismatch()
147     {
148         class_exists(__NAMESPACE__.'\TestingCaseMismatch', true);
149     }
150
151     /**
152      * @expectedException \RuntimeException
153      * @expectedExceptionMessage Case mismatch between class and real file names
154      */
155     public function testFileCaseMismatch()
156     {
157         if (!file_exists(__DIR__.'/Fixtures/CaseMismatch.php')) {
158             $this->markTestSkipped('Can only be run on case insensitive filesystems');
159         }
160
161         class_exists(__NAMESPACE__.'\Fixtures\CaseMismatch', true);
162     }
163
164     /**
165      * @expectedException \RuntimeException
166      * @expectedExceptionMessage Case mismatch between loaded and declared class names
167      */
168     public function testPsr4CaseMismatch()
169     {
170         class_exists(__NAMESPACE__.'\Fixtures\Psr4CaseMismatch', true);
171     }
172
173     public function testNotPsr0()
174     {
175         $this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\NotPSR0', true));
176     }
177
178     public function testNotPsr0Bis()
179     {
180         $this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\NotPSR0bis', true));
181     }
182
183     public function testClassAlias()
184     {
185         $this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\ClassAlias', true));
186     }
187
188     /**
189      * @dataProvider provideDeprecatedSuper
190      */
191     public function testDeprecatedSuper($class, $super, $type)
192     {
193         set_error_handler(function () { return false; });
194         $e = error_reporting(0);
195         trigger_error('', E_USER_DEPRECATED);
196
197         class_exists('Test\\'.__NAMESPACE__.'\\'.$class, true);
198
199         error_reporting($e);
200         restore_error_handler();
201
202         $lastError = error_get_last();
203         unset($lastError['file'], $lastError['line']);
204
205         $xError = array(
206             'type' => E_USER_DEPRECATED,
207             'message' => 'The "Test\Symfony\Component\Debug\Tests\\'.$class.'" class '.$type.' "Symfony\Component\Debug\Tests\Fixtures\\'.$super.'" that is deprecated but this is a test deprecation notice.',
208         );
209
210         $this->assertSame($xError, $lastError);
211     }
212
213     public function provideDeprecatedSuper()
214     {
215         return array(
216             array('DeprecatedInterfaceClass', 'DeprecatedInterface', 'implements'),
217             array('DeprecatedParentClass', 'DeprecatedClass', 'extends'),
218         );
219     }
220
221     public function testInterfaceExtendsDeprecatedInterface()
222     {
223         set_error_handler(function () { return false; });
224         $e = error_reporting(0);
225         trigger_error('', E_USER_NOTICE);
226
227         class_exists('Test\\'.__NAMESPACE__.'\\NonDeprecatedInterfaceClass', true);
228
229         error_reporting($e);
230         restore_error_handler();
231
232         $lastError = error_get_last();
233         unset($lastError['file'], $lastError['line']);
234
235         $xError = array(
236             'type' => E_USER_NOTICE,
237             'message' => '',
238         );
239
240         $this->assertSame($xError, $lastError);
241     }
242
243     public function testDeprecatedSuperInSameNamespace()
244     {
245         set_error_handler(function () { return false; });
246         $e = error_reporting(0);
247         trigger_error('', E_USER_NOTICE);
248
249         class_exists('Symfony\Bridge\Debug\Tests\Fixtures\ExtendsDeprecatedParent', true);
250
251         error_reporting($e);
252         restore_error_handler();
253
254         $lastError = error_get_last();
255         unset($lastError['file'], $lastError['line']);
256
257         $xError = array(
258             'type' => E_USER_NOTICE,
259             'message' => '',
260         );
261
262         $this->assertSame($xError, $lastError);
263     }
264
265     public function testReservedForPhp7()
266     {
267         if (\PHP_VERSION_ID >= 70000) {
268             $this->markTestSkipped('PHP7 already prevents using reserved names.');
269         }
270
271         set_error_handler(function () { return false; });
272         $e = error_reporting(0);
273         trigger_error('', E_USER_NOTICE);
274
275         class_exists('Test\\'.__NAMESPACE__.'\\Float', true);
276
277         error_reporting($e);
278         restore_error_handler();
279
280         $lastError = error_get_last();
281         unset($lastError['file'], $lastError['line']);
282
283         $xError = array(
284             'type' => E_USER_DEPRECATED,
285             'message' => 'The "Test\Symfony\Component\Debug\Tests\Float" class uses the reserved name "Float", it will break on PHP 7 and higher',
286         );
287
288         $this->assertSame($xError, $lastError);
289     }
290
291     public function testExtendedFinalClass()
292     {
293         set_error_handler(function () { return false; });
294         $e = error_reporting(0);
295         trigger_error('', E_USER_NOTICE);
296
297         class_exists('Test\\'.__NAMESPACE__.'\\ExtendsFinalClass', true);
298
299         error_reporting($e);
300         restore_error_handler();
301
302         $lastError = error_get_last();
303         unset($lastError['file'], $lastError['line']);
304
305         $xError = array(
306             'type' => E_USER_DEPRECATED,
307             'message' => 'The "Symfony\Component\Debug\Tests\Fixtures\FinalClass" class is considered final since version 3.3. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsFinalClass".',
308         );
309
310         $this->assertSame($xError, $lastError);
311     }
312
313     public function testExtendedFinalMethod()
314     {
315         set_error_handler(function () { return false; });
316         $e = error_reporting(0);
317         trigger_error('', E_USER_NOTICE);
318
319         class_exists(__NAMESPACE__.'\\Fixtures\\ExtendedFinalMethod', true);
320
321         error_reporting($e);
322         restore_error_handler();
323
324         $lastError = error_get_last();
325         unset($lastError['file'], $lastError['line']);
326
327         $xError = array(
328             'type' => E_USER_DEPRECATED,
329             'message' => 'The "Symfony\Component\Debug\Tests\Fixtures\FinalMethod::finalMethod()" method is considered final since version 3.3. It may change without further notice as of its next major version. You should not extend it from "Symfony\Component\Debug\Tests\Fixtures\ExtendedFinalMethod".',
330         );
331
332         $this->assertSame($xError, $lastError);
333     }
334
335     public function testExtendedDeprecatedMethodDoesntTriggerAnyNotice()
336     {
337         set_error_handler(function () { return false; });
338         $e = error_reporting(0);
339         trigger_error('', E_USER_NOTICE);
340
341         class_exists('Test\\'.__NAMESPACE__.'\\ExtendsAnnotatedClass', true);
342
343         error_reporting($e);
344         restore_error_handler();
345
346         $lastError = error_get_last();
347         unset($lastError['file'], $lastError['line']);
348
349         $this->assertSame(array('type' => E_USER_NOTICE, 'message' => ''), $lastError);
350     }
351
352     public function testInternalsUse()
353     {
354         $deprecations = array();
355         set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
356         $e = error_reporting(E_USER_DEPRECATED);
357
358         class_exists('Test\\'.__NAMESPACE__.'\\ExtendsInternals', true);
359
360         error_reporting($e);
361         restore_error_handler();
362
363         $this->assertSame($deprecations, array(
364             'The "Symfony\Component\Debug\Tests\Fixtures\InternalClass" class is considered internal since version 3.4. It may change without further notice. You should not use it from "Test\Symfony\Component\Debug\Tests\ExtendsInternalsParent".',
365             'The "Symfony\Component\Debug\Tests\Fixtures\InternalInterface" interface is considered internal. It may change without further notice. You should not use it from "Test\Symfony\Component\Debug\Tests\ExtendsInternalsParent".',
366             'The "Symfony\Component\Debug\Tests\Fixtures\InternalTrait" trait is considered internal. It may change without further notice. You should not use it from "Test\Symfony\Component\Debug\Tests\ExtendsInternals".',
367             'The "Symfony\Component\Debug\Tests\Fixtures\InternalTrait2::internalMethod()" method is considered internal since version 3.4. It may change without further notice. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsInternals".',
368         ));
369     }
370 }
371
372 class ClassLoader
373 {
374     public function loadClass($class)
375     {
376     }
377
378     public function getClassMap()
379     {
380         return array(__NAMESPACE__.'\Fixtures\NotPSR0bis' => __DIR__.'/Fixtures/notPsr0Bis.php');
381     }
382
383     public function findFile($class)
384     {
385         $fixtureDir = __DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR;
386
387         if (__NAMESPACE__.'\TestingUnsilencing' === $class) {
388             eval('-- parse error --');
389         } elseif (__NAMESPACE__.'\TestingStacking' === $class) {
390             eval('namespace '.__NAMESPACE__.'; class TestingStacking { function foo() {} }');
391         } elseif (__NAMESPACE__.'\TestingCaseMismatch' === $class) {
392             eval('namespace '.__NAMESPACE__.'; class TestingCaseMisMatch {}');
393         } elseif (__NAMESPACE__.'\Fixtures\Psr4CaseMismatch' === $class) {
394             return $fixtureDir.'psr4'.DIRECTORY_SEPARATOR.'Psr4CaseMismatch.php';
395         } elseif (__NAMESPACE__.'\Fixtures\NotPSR0' === $class) {
396             return $fixtureDir.'reallyNotPsr0.php';
397         } elseif (__NAMESPACE__.'\Fixtures\NotPSR0bis' === $class) {
398             return $fixtureDir.'notPsr0Bis.php';
399         } elseif ('Symfony\Bridge\Debug\Tests\Fixtures\ExtendsDeprecatedParent' === $class) {
400             eval('namespace Symfony\Bridge\Debug\Tests\Fixtures; class ExtendsDeprecatedParent extends \\'.__NAMESPACE__.'\Fixtures\DeprecatedClass {}');
401         } elseif ('Test\\'.__NAMESPACE__.'\DeprecatedParentClass' === $class) {
402             eval('namespace Test\\'.__NAMESPACE__.'; class DeprecatedParentClass extends \\'.__NAMESPACE__.'\Fixtures\DeprecatedClass {}');
403         } elseif ('Test\\'.__NAMESPACE__.'\DeprecatedInterfaceClass' === $class) {
404             eval('namespace Test\\'.__NAMESPACE__.'; class DeprecatedInterfaceClass implements \\'.__NAMESPACE__.'\Fixtures\DeprecatedInterface {}');
405         } elseif ('Test\\'.__NAMESPACE__.'\NonDeprecatedInterfaceClass' === $class) {
406             eval('namespace Test\\'.__NAMESPACE__.'; class NonDeprecatedInterfaceClass implements \\'.__NAMESPACE__.'\Fixtures\NonDeprecatedInterface {}');
407         } elseif ('Test\\'.__NAMESPACE__.'\Float' === $class) {
408             eval('namespace Test\\'.__NAMESPACE__.'; class Float {}');
409         } elseif ('Test\\'.__NAMESPACE__.'\ExtendsFinalClass' === $class) {
410             eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsFinalClass extends \\'.__NAMESPACE__.'\Fixtures\FinalClass {}');
411         } elseif ('Test\\'.__NAMESPACE__.'\ExtendsAnnotatedClass' === $class) {
412             eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsAnnotatedClass extends \\'.__NAMESPACE__.'\Fixtures\AnnotatedClass {
413                 public function deprecatedMethod() { }
414             }');
415         } elseif ('Test\\'.__NAMESPACE__.'\ExtendsInternals' === $class) {
416             eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsInternals extends ExtendsInternalsParent {
417                 use \\'.__NAMESPACE__.'\Fixtures\InternalTrait;
418
419                 public function internalMethod() { }
420             }');
421         } elseif ('Test\\'.__NAMESPACE__.'\ExtendsInternalsParent' === $class) {
422             eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsInternalsParent extends \\'.__NAMESPACE__.'\Fixtures\InternalClass implements \\'.__NAMESPACE__.'\Fixtures\InternalInterface { }');
423         }
424     }
425 }