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