3 namespace Drupal\system\Tests\System;
5 use Drupal\Component\Render\FormattableMarkup;
6 use Drupal\simpletest\WebTestBase;
9 * Performs tests on the Drupal error and exception handler.
13 class ErrorHandlerTest extends WebTestBase {
20 public static $modules = ['error_test'];
23 * Test the error handler.
25 public function testErrorHandler() {
26 $config = $this->config('system.logging');
29 '@message' => 'Undefined variable: bananas',
30 '%function' => 'Drupal\error_test\Controller\ErrorTestController->generateWarnings()',
31 '%file' => drupal_get_path('module', 'error_test') . '/error_test.module',
35 '@message' => 'Division by zero',
36 '%function' => 'Drupal\error_test\Controller\ErrorTestController->generateWarnings()',
37 '%file' => drupal_get_path('module', 'error_test') . '/error_test.module',
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',
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',
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';
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('&');
67 $this->assertNoRaw('&amp;');
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(''');
77 $this->assertNoRaw('&#039;');
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
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']);
88 // Drop the single exception.
89 $this->results['#exception']--;
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']);
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.');
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.');
122 * Test the exception handler.
124 public function testExceptionHandler() {
125 // Ensure the test error log is empty before these tests.
126 $this->assertNoErrorsLogged();
129 '%type' => 'Exception',
130 '@message' => 'Drupal & awesome',
131 '%function' => 'Drupal\error_test\Controller\ErrorTestController->triggerException()',
133 '%file' => drupal_get_path('module', 'error_test') . '/error_test.module',
135 $error_pdo_exception = [
136 '%type' => 'DatabaseExceptionWrapper',
137 '@message' => 'SELECT * FROM bananas_are_awesome',
138 '%function' => 'Drupal\error_test\Controller\ErrorTestController->triggerPDOException()',
140 '%file' => drupal_get_path('module', 'error_test') . '/error_test.module',
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}()',
147 '%file' => drupal_get_path('module', 'error_test') . '/error_test.module',
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);
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]));
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);
167 // Disable error reporting, ensure that 5xx responses are not cached.
168 $this->config('system.logging')
169 ->set('error_level', ERROR_REPORTING_HIDE)
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);
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');
184 * Helper function: assert that the error message is found.
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]));
192 * Helper function: assert that the error message is not found.
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]));
200 * Asserts that no messages are printed onto the page.
203 * TRUE, if there are no messages.
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.');