Further Drupal 8.6.4 changes. Some core files were not committed before a commit...
[yaffs-website] / web / core / modules / system / src / Tests / System / ErrorHandlerTest.php
1 <?php
2
3 namespace Drupal\system\Tests\System;
4
5 use Drupal\Component\Render\FormattableMarkup;
6 use Drupal\simpletest\WebTestBase;
7
8 /**
9  * Performs tests on the Drupal error and exception handler.
10  *
11  * @group system
12  */
13 class ErrorHandlerTest extends WebTestBase {
14
15   /**
16    * Modules to enable.
17    *
18    * @var array
19    */
20   public static $modules = ['error_test'];
21
22   /**
23    * Test the error handler.
24    */
25   public function testErrorHandler() {
26     $config = $this->config('system.logging');
27     $error_notice = [
28       '%type' => 'Notice',
29       '@message' => 'Undefined variable: bananas',
30       '%function' => 'Drupal\error_test\Controller\ErrorTestController->generateWarnings()',
31       '%file' => drupal_get_path('module', 'error_test') . '/error_test.module',
32     ];
33     $error_warning = [
34       '%type' => 'Warning',
35       '@message' => 'Division by zero',
36       '%function' => 'Drupal\error_test\Controller\ErrorTestController->generateWarnings()',
37       '%file' => drupal_get_path('module', 'error_test') . '/error_test.module',
38     ];
39     $error_user_notice = [
40       '%type' => 'User warning',
41       '@message' => 'Drupal & awesome',
42       '%function' => 'Drupal\error_test\Controller\ErrorTestController->generateWarnings()',
43       '%file' => drupal_get_path('module', 'error_test') . '/error_test.module',
44     ];
45     $fatal_error = [
46       '%type' => 'Recoverable fatal error',
47       '%function' => 'Drupal\error_test\Controller\ErrorTestController->Drupal\error_test\Controller\{closure}()',
48       '@message' => 'Argument 1 passed to Drupal\error_test\Controller\ErrorTestController::Drupal\error_test\Controller\{closure}() must be of the type array, string given, called in ' . \Drupal::root() . '/core/modules/system/tests/modules/error_test/src/Controller/ErrorTestController.php on line 62 and defined',
49     ];
50     if (version_compare(PHP_VERSION, '7.0.0-dev') >= 0) {
51       // In PHP 7, instead of a recoverable fatal error we get a TypeError.
52       $fatal_error['%type'] = 'TypeError';
53       // The error message also changes in PHP 7.
54       $fatal_error['@message'] = 'Argument 1 passed to Drupal\error_test\Controller\ErrorTestController::Drupal\error_test\Controller\{closure}() must be of the type array, string given, called in ' . \Drupal::root() . '/core/modules/system/tests/modules/error_test/src/Controller/ErrorTestController.php on line 62';
55     }
56
57     // Set error reporting to display verbose notices.
58     $this->config('system.logging')->set('error_level', ERROR_REPORTING_DISPLAY_VERBOSE)->save();
59     $this->drupalGet('error-test/generate-warnings');
60     $this->assertResponse(200, 'Received expected HTTP status code.');
61     $this->assertErrorMessage($error_notice);
62     $this->assertErrorMessage($error_warning);
63     $this->assertErrorMessage($error_user_notice);
64     $this->assertRaw('<pre class="backtrace">', 'Found pre element with backtrace class.');
65     // Ensure we are escaping but not double escaping.
66     $this->assertRaw('&amp;');
67     $this->assertNoRaw('&amp;amp;');
68
69     // Set error reporting to display verbose notices.
70     $this->config('system.logging')->set('error_level', ERROR_REPORTING_DISPLAY_VERBOSE)->save();
71     $this->drupalGet('error-test/generate-fatals');
72     $this->assertResponse(500, 'Received expected HTTP status code.');
73     $this->assertErrorMessage($fatal_error);
74     $this->assertRaw('<pre class="backtrace">', 'Found pre element with backtrace class.');
75     // Ensure we are escaping but not double escaping.
76     $this->assertRaw('&#039;');
77     $this->assertNoRaw('&amp;#039;');
78
79     // Remove the recoverable fatal error from the assertions, it's wanted here.
80     // Ensure that we just remove this one recoverable fatal error (in PHP 7 this
81     // is a TypeError).
82     foreach ($this->assertions as $key => $assertion) {
83       if (in_array($assertion['message_group'], ['Recoverable fatal error', 'TypeError']) && strpos($assertion['message'], 'Argument 1 passed to Drupal\error_test\Controller\ErrorTestController::Drupal\error_test\Controller\{closure}() must be of the type array, string given, called in') !== FALSE) {
84         unset($this->assertions[$key]);
85         $this->deleteAssert($assertion['message_id']);
86       }
87     }
88     // Drop the single exception.
89     $this->results['#exception']--;
90
91     // Set error reporting to collect notices.
92     $config->set('error_level', ERROR_REPORTING_DISPLAY_ALL)->save();
93     $this->drupalGet('error-test/generate-warnings');
94     $this->assertResponse(200, 'Received expected HTTP status code.');
95     $this->assertErrorMessage($error_notice);
96     $this->assertErrorMessage($error_warning);
97     $this->assertErrorMessage($error_user_notice);
98     $this->assertNoRaw('<pre class="backtrace">', 'Did not find pre element with backtrace class.');
99     $this->assertErrorLogged($fatal_error['@message']);
100
101     // Set error reporting to not collect notices.
102     $config->set('error_level', ERROR_REPORTING_DISPLAY_SOME)->save();
103     $this->drupalGet('error-test/generate-warnings');
104     $this->assertResponse(200, 'Received expected HTTP status code.');
105     $this->assertNoErrorMessage($error_notice);
106     $this->assertErrorMessage($error_warning);
107     $this->assertErrorMessage($error_user_notice);
108     $this->assertNoRaw('<pre class="backtrace">', 'Did not find pre element with backtrace class.');
109
110     // Set error reporting to not show any errors.
111     $config->set('error_level', ERROR_REPORTING_HIDE)->save();
112     $this->drupalGet('error-test/generate-warnings');
113     $this->assertResponse(200, 'Received expected HTTP status code.');
114     $this->assertNoErrorMessage($error_notice);
115     $this->assertNoErrorMessage($error_warning);
116     $this->assertNoErrorMessage($error_user_notice);
117     $this->assertNoMessages();
118     $this->assertNoRaw('<pre class="backtrace">', 'Did not find pre element with backtrace class.');
119   }
120
121   /**
122    * Test the exception handler.
123    */
124   public function testExceptionHandler() {
125     // Ensure the test error log is empty before these tests.
126     $this->assertNoErrorsLogged();
127
128     $error_exception = [
129       '%type' => 'Exception',
130       '@message' => 'Drupal & awesome',
131       '%function' => 'Drupal\error_test\Controller\ErrorTestController->triggerException()',
132       '%line' => 56,
133       '%file' => drupal_get_path('module', 'error_test') . '/error_test.module',
134     ];
135     $error_pdo_exception = [
136       '%type' => 'DatabaseExceptionWrapper',
137       '@message' => 'SELECT * FROM bananas_are_awesome',
138       '%function' => 'Drupal\error_test\Controller\ErrorTestController->triggerPDOException()',
139       '%line' => 64,
140       '%file' => drupal_get_path('module', 'error_test') . '/error_test.module',
141     ];
142     $error_renderer_exception = [
143       '%type' => 'Exception',
144       '@message' => 'This is an exception that occurs during rendering',
145       '%function' => 'Drupal\error_test\Controller\ErrorTestController->Drupal\error_test\Controller\{closure}()',
146       '%line' => 82,
147       '%file' => drupal_get_path('module', 'error_test') . '/error_test.module',
148     ];
149
150     $this->drupalGet('error-test/trigger-exception');
151     $this->assertTrue(strpos($this->drupalGetHeader(':status'), '500 Service unavailable (with message)'), 'Received expected HTTP status line.');
152     $this->assertErrorMessage($error_exception);
153
154     $this->drupalGet('error-test/trigger-pdo-exception');
155     $this->assertTrue(strpos($this->drupalGetHeader(':status'), '500 Service unavailable (with message)'), 'Received expected HTTP status line.');
156     // We cannot use assertErrorMessage() since the exact error reported
157     // varies from database to database. Check that the SQL string is displayed.
158     $this->assertText($error_pdo_exception['%type'], format_string('Found %type in error page.', $error_pdo_exception));
159     $this->assertText($error_pdo_exception['@message'], format_string('Found @message in error page.', $error_pdo_exception));
160     $error_details = format_string('in %function (line ', $error_pdo_exception);
161     $this->assertRaw($error_details, format_string("Found '@message' in error page.", ['@message' => $error_details]));
162
163     $this->drupalGet('error-test/trigger-renderer-exception');
164     $this->assertTrue(strpos($this->drupalGetHeader(':status'), '500 Service unavailable (with message)'), 'Received expected HTTP status line.');
165     $this->assertErrorMessage($error_renderer_exception);
166
167     // Disable error reporting, ensure that 5xx responses are not cached.
168     $this->config('system.logging')
169       ->set('error_level', ERROR_REPORTING_HIDE)
170       ->save();
171
172     $this->drupalGet('error-test/trigger-exception');
173     $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'));
174     $this->assertIdentical(strpos($this->drupalGetHeader('Cache-Control'), 'public'), FALSE, 'Received expected HTTP status line.');
175     $this->assertTrue(strpos($this->drupalGetHeader(':status'), '500 Service unavailable (with message)'), 'Received expected HTTP status line.');
176     $this->assertNoErrorMessage($error_exception);
177
178     // The exceptions are expected. Do not interpret them as a test failure.
179     // Not using File API; a potential error must trigger a PHP warning.
180     unlink(\Drupal::root() . '/' . $this->siteDirectory . '/error.log');
181   }
182
183   /**
184    * Helper function: assert that the error message is found.
185    */
186   public function assertErrorMessage(array $error) {
187     $message = new FormattableMarkup('%type: @message in %function (line ', $error);
188     $this->assertRaw($message, format_string('Found error message: @message.', ['@message' => $message]));
189   }
190
191   /**
192    * Helper function: assert that the error message is not found.
193    */
194   public function assertNoErrorMessage(array $error) {
195     $message = new FormattableMarkup('%type: @message in %function (line ', $error);
196     $this->assertNoRaw($message, format_string('Did not find error message: @message.', ['@message' => $message]));
197   }
198
199   /**
200    * Asserts that no messages are printed onto the page.
201    *
202    * @return bool
203    *   TRUE, if there are no messages.
204    */
205   protected function assertNoMessages() {
206     return $this->assertFalse($this->xpath('//div[contains(@class, "messages")]'), 'Ensures that also no messages div exists, which proves that no messages were generated by the error handler, not even an empty one.');
207   }
208
209 }