1<?php 2 3namespace Drupal\Tests\Traits; 4 5use Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListener as LegacySymfonyTestsListener; 6use Symfony\Bridge\PhpUnit\SymfonyTestsListener; 7use PHPUnit\Framework\AssertionFailedError; 8use PHPUnit\Framework\TestCase; 9use PHPUnit\Util\Test; 10 11/** 12 * Adds the ability to dynamically set expected deprecation messages in tests. 13 * 14 * @internal 15 * This class should only be used by Drupal core and will be removed once 16 * https://github.com/symfony/symfony/pull/25757 is resolved. 17 * 18 * @todo Remove once https://github.com/symfony/symfony/pull/25757 is resolved. 19 */ 20trait ExpectDeprecationTrait { 21 22 /** 23 * Sets an expected deprecation message. 24 * 25 * @param string $message 26 * The expected deprecation message. 27 */ 28 protected function addExpectedDeprecationMessage($message) { 29 $this->expectedDeprecations([$message]); 30 } 31 32 /** 33 * Sets an expected deprecation message. 34 * 35 * @param string $message 36 * The expected deprecation message. 37 * 38 * @deprecated in drupal:8.8.5 and is removed from drupal:9.0.0. Use 39 * ::addExpectedDeprecationMessage() instead. 40 * 41 * @see https://www.drupal.org/node/3106024 42 */ 43 protected function expectDeprecation($message) { 44 if (strpos($message, 'ExpectDeprecationTrait::expectDeprecation') === FALSE) { 45 @trigger_error('ExpectDeprecationTrait::expectDeprecation is deprecated in drupal:8.8.5 and is removed from drupal:9.0.0. Use ::addExpectedDeprecationMessage() instead. See https://www.drupal.org/node/3106024', E_USER_DEPRECATED); 46 } 47 $this->addExpectedDeprecationMessage($message); 48 } 49 50 /** 51 * Sets expected deprecation messages. 52 * 53 * @param string[] $messages 54 * The expected deprecation messages. 55 */ 56 public function expectedDeprecations(array $messages) { 57 if ($this instanceof TestCase) { 58 // Ensure the class or method is in the legacy group. 59 $groups = Test::getGroups(get_class($this), $this->getName(FALSE)); 60 if (!in_array('legacy', $groups, TRUE)) { 61 throw new AssertionFailedError('Only tests with the `@group legacy` annotation can call `setExpectedDeprecation()`.'); 62 } 63 64 // If setting an expected deprecation there is no need to be strict about 65 // testing nothing as this is an assertion. 66 $this->getTestResultObject() 67 ->beStrictAboutTestsThatDoNotTestAnything(FALSE); 68 69 if ($trait = $this->getSymfonyTestListenerTrait()) { 70 // Add the expected deprecation message to the class property. 71 $reflection_class = new \ReflectionClass($trait); 72 $expected_deprecations_property = $reflection_class->getProperty('expectedDeprecations'); 73 $expected_deprecations_property->setAccessible(TRUE); 74 $expected_deprecations = $expected_deprecations_property->getValue($trait); 75 $expected_deprecations = array_merge($expected_deprecations, $messages); 76 $expected_deprecations_property->setValue($trait, $expected_deprecations); 77 78 // Register the error handler if necessary. 79 $previous_error_handler_property = $reflection_class->getProperty('previousErrorHandler'); 80 $previous_error_handler_property->setAccessible(TRUE); 81 $previous_error_handler = $previous_error_handler_property->getValue($trait); 82 if (!$previous_error_handler) { 83 $previous_error_handler = set_error_handler([$trait, 'handleError']); 84 $previous_error_handler_property->setValue($trait, $previous_error_handler); 85 } 86 return; 87 } 88 } 89 90 // Expected deprecations set by isolated tests need to be written to a file 91 // so that the test running process can take account of them. 92 if ($file = getenv('DRUPAL_EXPECTED_DEPRECATIONS_SERIALIZE')) { 93 $expected_deprecations = file_get_contents($file); 94 if ($expected_deprecations) { 95 $expected_deprecations = array_merge(unserialize($expected_deprecations), $messages); 96 } 97 else { 98 $expected_deprecations = $messages; 99 } 100 file_put_contents($file, serialize($expected_deprecations)); 101 return; 102 } 103 104 throw new AssertionFailedError('Can not set an expected deprecation message because the Symfony\Bridge\PhpUnit\SymfonyTestsListener is not registered as a PHPUnit test listener.'); 105 } 106 107 /** 108 * Gets the SymfonyTestsListenerTrait. 109 * 110 * @return \Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait|null 111 * The SymfonyTestsListenerTrait object or NULL is a Symfony test listener 112 * is not present. 113 */ 114 private function getSymfonyTestListenerTrait() { 115 $test_result_object = $this->getTestResultObject(); 116 $reflection_class = new \ReflectionClass($test_result_object); 117 $reflection_property = $reflection_class->getProperty('listeners'); 118 $reflection_property->setAccessible(TRUE); 119 $listeners = $reflection_property->getValue($test_result_object); 120 foreach ($listeners as $listener) { 121 if ($listener instanceof SymfonyTestsListener || $listener instanceof LegacySymfonyTestsListener) { 122 $reflection_class = new \ReflectionClass($listener); 123 $reflection_property = $reflection_class->getProperty('trait'); 124 $reflection_property->setAccessible(TRUE); 125 return $reflection_property->getValue($listener); 126 } 127 } 128 } 129 130} 131