annotate core/modules/system/src/Tests/System/UncaughtExceptionTest.php @ 12:7a779792577d

Update Drupal core to v8.4.5 (via Composer)
author Chris Cannam
date Fri, 23 Feb 2018 15:52:07 +0000
parents 4c8ae668cc8c
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\system\Tests\System;
Chris@0 4
Chris@0 5 use Drupal\simpletest\WebTestBase;
Chris@0 6
Chris@0 7 /**
Chris@0 8 * Tests kernel panic when things are really messed up.
Chris@0 9 *
Chris@0 10 * @group system
Chris@0 11 */
Chris@0 12 class UncaughtExceptionTest extends WebTestBase {
Chris@0 13
Chris@0 14 /**
Chris@0 15 * Exceptions thrown by site under test that contain this text are ignored.
Chris@0 16 *
Chris@0 17 * @var string
Chris@0 18 */
Chris@0 19 protected $expectedExceptionMessage;
Chris@0 20
Chris@0 21 /**
Chris@0 22 * Modules to enable.
Chris@0 23 *
Chris@0 24 * @var array
Chris@0 25 */
Chris@0 26 public static $modules = ['error_service_test'];
Chris@0 27
Chris@0 28 /**
Chris@0 29 * {@inheritdoc}
Chris@0 30 */
Chris@0 31 protected function setUp() {
Chris@0 32 parent::setUp();
Chris@0 33
Chris@0 34 $settings_filename = $this->siteDirectory . '/settings.php';
Chris@0 35 chmod($settings_filename, 0777);
Chris@0 36 $settings_php = file_get_contents($settings_filename);
Chris@0 37 $settings_php .= "\ninclude_once 'core/modules/system/src/Tests/Bootstrap/ErrorContainer.php';\n";
Chris@0 38 $settings_php .= "\ninclude_once 'core/modules/system/src/Tests/Bootstrap/ExceptionContainer.php';\n";
Chris@0 39 file_put_contents($settings_filename, $settings_php);
Chris@0 40
Chris@0 41 $settings = [];
Chris@0 42 $settings['config']['system.logging']['error_level'] = (object) [
Chris@0 43 'value' => ERROR_REPORTING_DISPLAY_VERBOSE,
Chris@0 44 'required' => TRUE,
Chris@0 45 ];
Chris@0 46 $this->writeSettings($settings);
Chris@0 47 }
Chris@0 48
Chris@0 49 /**
Chris@0 50 * Tests uncaught exception handling when system is in a bad state.
Chris@0 51 */
Chris@0 52 public function testUncaughtException() {
Chris@0 53 $this->expectedExceptionMessage = 'Oh oh, bananas in the instruments.';
Chris@0 54 \Drupal::state()->set('error_service_test.break_bare_html_renderer', TRUE);
Chris@0 55
Chris@0 56 $this->config('system.logging')
Chris@0 57 ->set('error_level', ERROR_REPORTING_HIDE)
Chris@0 58 ->save();
Chris@0 59 $settings = [];
Chris@0 60 $settings['config']['system.logging']['error_level'] = (object) [
Chris@0 61 'value' => ERROR_REPORTING_HIDE,
Chris@0 62 'required' => TRUE,
Chris@0 63 ];
Chris@0 64 $this->writeSettings($settings);
Chris@0 65
Chris@0 66 $this->drupalGet('');
Chris@0 67 $this->assertResponse(500);
Chris@0 68 $this->assertText('The website encountered an unexpected error. Please try again later.');
Chris@0 69 $this->assertNoText($this->expectedExceptionMessage);
Chris@0 70
Chris@0 71 $this->config('system.logging')
Chris@0 72 ->set('error_level', ERROR_REPORTING_DISPLAY_ALL)
Chris@0 73 ->save();
Chris@0 74 $settings = [];
Chris@0 75 $settings['config']['system.logging']['error_level'] = (object) [
Chris@0 76 'value' => ERROR_REPORTING_DISPLAY_ALL,
Chris@0 77 'required' => TRUE,
Chris@0 78 ];
Chris@0 79 $this->writeSettings($settings);
Chris@0 80
Chris@0 81 $this->drupalGet('');
Chris@0 82 $this->assertResponse(500);
Chris@0 83 $this->assertText('The website encountered an unexpected error. Please try again later.');
Chris@0 84 $this->assertText($this->expectedExceptionMessage);
Chris@0 85 $this->assertErrorLogged($this->expectedExceptionMessage);
Chris@0 86 }
Chris@0 87
Chris@0 88 /**
Chris@0 89 * Tests uncaught exception handling with custom exception handler.
Chris@0 90 */
Chris@0 91 public function testUncaughtExceptionCustomExceptionHandler() {
Chris@0 92 $settings_filename = $this->siteDirectory . '/settings.php';
Chris@0 93 chmod($settings_filename, 0777);
Chris@0 94 $settings_php = file_get_contents($settings_filename);
Chris@0 95 $settings_php .= "\n";
Chris@0 96 $settings_php .= "set_exception_handler(function() {\n";
Chris@0 97 $settings_php .= " header('HTTP/1.1 418 I\'m a teapot');\n";
Chris@0 98 $settings_php .= " print('Oh oh, flying teapots');\n";
Chris@0 99 $settings_php .= "});\n";
Chris@0 100 file_put_contents($settings_filename, $settings_php);
Chris@0 101
Chris@0 102 \Drupal::state()->set('error_service_test.break_bare_html_renderer', TRUE);
Chris@0 103
Chris@0 104 $this->drupalGet('');
Chris@0 105 $this->assertResponse(418);
Chris@0 106 $this->assertNoText('The website encountered an unexpected error. Please try again later.');
Chris@0 107 $this->assertNoText('Oh oh, bananas in the instruments');
Chris@0 108 $this->assertText('Oh oh, flying teapots');
Chris@0 109 }
Chris@0 110
Chris@0 111 /**
Chris@0 112 * Tests a missing dependency on a service.
Chris@0 113 */
Chris@0 114 public function testMissingDependency() {
Chris@0 115 if (version_compare(PHP_VERSION, '7.1') < 0) {
Chris@0 116 $this->expectedExceptionMessage = 'Argument 1 passed to Drupal\error_service_test\LonelyMonkeyClass::__construct() must be an instance of Drupal\Core\Database\Connection, non';
Chris@0 117 }
Chris@0 118 else {
Chris@0 119 $this->expectedExceptionMessage = 'Too few arguments to function Drupal\error_service_test\LonelyMonkeyClass::__construct(), 0 passed';
Chris@0 120 }
Chris@0 121 $this->drupalGet('broken-service-class');
Chris@0 122 $this->assertResponse(500);
Chris@0 123
Chris@0 124 $this->assertRaw('The website encountered an unexpected error.');
Chris@0 125 $this->assertRaw($this->expectedExceptionMessage);
Chris@0 126 $this->assertErrorLogged($this->expectedExceptionMessage);
Chris@0 127 }
Chris@0 128
Chris@0 129 /**
Chris@0 130 * Tests a missing dependency on a service with a custom error handler.
Chris@0 131 */
Chris@0 132 public function testMissingDependencyCustomErrorHandler() {
Chris@0 133 $settings_filename = $this->siteDirectory . '/settings.php';
Chris@0 134 chmod($settings_filename, 0777);
Chris@0 135 $settings_php = file_get_contents($settings_filename);
Chris@0 136 $settings_php .= "\n";
Chris@0 137 $settings_php .= "set_error_handler(function() {\n";
Chris@0 138 $settings_php .= " header('HTTP/1.1 418 I\'m a teapot');\n";
Chris@0 139 $settings_php .= " print('Oh oh, flying teapots');\n";
Chris@0 140 $settings_php .= " exit();\n";
Chris@0 141 $settings_php .= "});\n";
Chris@0 142 $settings_php .= "\$settings['teapots'] = TRUE;\n";
Chris@0 143 file_put_contents($settings_filename, $settings_php);
Chris@0 144
Chris@0 145 $this->drupalGet('broken-service-class');
Chris@0 146 $this->assertResponse(418);
Chris@0 147 $this->assertRaw('Oh oh, flying teapots');
Chris@0 148
Chris@0 149 $message = 'Argument 1 passed to Drupal\error_service_test\LonelyMonkeyClass::__construct() must be an instance of Drupal\Core\Database\Connection, non';
Chris@0 150
Chris@0 151 $this->assertNoRaw('The website encountered an unexpected error.');
Chris@0 152 $this->assertNoRaw($message);
Chris@0 153
Chris@0 154 $found_exception = FALSE;
Chris@0 155 foreach ($this->assertions as &$assertion) {
Chris@0 156 if (strpos($assertion['message'], $message) !== FALSE) {
Chris@0 157 $found_exception = TRUE;
Chris@0 158 $this->deleteAssert($assertion['message_id']);
Chris@0 159 unset($assertion);
Chris@0 160 }
Chris@0 161 }
Chris@0 162
Chris@0 163 $this->assertTrue($found_exception, 'Ensure that the exception of a missing constructor argument was triggered.');
Chris@0 164 }
Chris@0 165
Chris@0 166 /**
Chris@0 167 * Tests a container which has an error.
Chris@0 168 */
Chris@0 169 public function testErrorContainer() {
Chris@0 170 $settings = [];
Chris@0 171 $settings['settings']['container_base_class'] = (object) [
Chris@0 172 'value' => '\Drupal\system\Tests\Bootstrap\ErrorContainer',
Chris@0 173 'required' => TRUE,
Chris@0 174 ];
Chris@0 175 $this->writeSettings($settings);
Chris@0 176 \Drupal::service('kernel')->invalidateContainer();
Chris@0 177
Chris@0 178 $this->expectedExceptionMessage = 'Argument 1 passed to Drupal\system\Tests\Bootstrap\ErrorContainer::Drupal\system\Tests\Bootstrap\{closur';
Chris@0 179 $this->drupalGet('');
Chris@0 180 $this->assertResponse(500);
Chris@0 181
Chris@0 182 $this->assertRaw($this->expectedExceptionMessage);
Chris@0 183 $this->assertErrorLogged($this->expectedExceptionMessage);
Chris@0 184 }
Chris@0 185
Chris@0 186 /**
Chris@0 187 * Tests a container which has an exception really early.
Chris@0 188 */
Chris@0 189 public function testExceptionContainer() {
Chris@0 190 $settings = [];
Chris@0 191 $settings['settings']['container_base_class'] = (object) [
Chris@0 192 'value' => '\Drupal\system\Tests\Bootstrap\ExceptionContainer',
Chris@0 193 'required' => TRUE,
Chris@0 194 ];
Chris@0 195 $this->writeSettings($settings);
Chris@0 196 \Drupal::service('kernel')->invalidateContainer();
Chris@0 197
Chris@0 198 $this->expectedExceptionMessage = 'Thrown exception during Container::get';
Chris@0 199 $this->drupalGet('');
Chris@0 200 $this->assertResponse(500);
Chris@0 201
Chris@0 202 $this->assertRaw('The website encountered an unexpected error');
Chris@0 203 $this->assertRaw($this->expectedExceptionMessage);
Chris@0 204 $this->assertErrorLogged($this->expectedExceptionMessage);
Chris@0 205 }
Chris@0 206
Chris@0 207 /**
Chris@0 208 * Tests the case when the database connection is gone.
Chris@0 209 */
Chris@0 210 public function testLostDatabaseConnection() {
Chris@0 211 $incorrect_username = $this->randomMachineName(16);
Chris@0 212 switch ($this->container->get('database')->driver()) {
Chris@0 213 case 'pgsql':
Chris@0 214 case 'mysql':
Chris@0 215 $this->expectedExceptionMessage = $incorrect_username;
Chris@0 216 break;
Chris@0 217 default:
Chris@0 218 // We can not carry out this test.
Chris@0 219 $this->pass('Unable to run \Drupal\system\Tests\System\UncaughtExceptionTest::testLostDatabaseConnection for this database type.');
Chris@0 220 return;
Chris@0 221 }
Chris@0 222
Chris@0 223 // We simulate a broken database connection by rewrite settings.php to no
Chris@0 224 // longer have the proper data.
Chris@0 225 $settings['databases']['default']['default']['username'] = (object) [
Chris@0 226 'value' => $incorrect_username,
Chris@0 227 'required' => TRUE,
Chris@0 228 ];
Chris@0 229 $settings['databases']['default']['default']['password'] = (object) [
Chris@0 230 'value' => $this->randomMachineName(16),
Chris@0 231 'required' => TRUE,
Chris@0 232 ];
Chris@0 233
Chris@0 234 $this->writeSettings($settings);
Chris@0 235
Chris@0 236 $this->drupalGet('');
Chris@0 237 $this->assertResponse(500);
Chris@0 238 $this->assertRaw('DatabaseAccessDeniedException');
Chris@0 239 $this->assertErrorLogged($this->expectedExceptionMessage);
Chris@0 240 }
Chris@0 241
Chris@0 242 /**
Chris@0 243 * Tests fallback to PHP error log when an exception is thrown while logging.
Chris@0 244 */
Chris@0 245 public function testLoggerException() {
Chris@0 246 // Ensure the test error log is empty before these tests.
Chris@0 247 $this->assertNoErrorsLogged();
Chris@0 248
Chris@0 249 $this->expectedExceptionMessage = 'Deforestation';
Chris@0 250 \Drupal::state()->set('error_service_test.break_logger', TRUE);
Chris@0 251
Chris@0 252 $this->drupalGet('');
Chris@0 253 $this->assertResponse(500);
Chris@0 254 $this->assertText('The website encountered an unexpected error. Please try again later.');
Chris@0 255 $this->assertRaw($this->expectedExceptionMessage);
Chris@0 256
Chris@0 257 // Find fatal error logged to the simpletest error.log
Chris@0 258 $errors = file(\Drupal::root() . '/' . $this->siteDirectory . '/error.log');
Chris@0 259 $this->assertIdentical(count($errors), 8, 'The error + the error that the logging service is broken has been written to the error log.');
Chris@0 260 $this->assertTrue(strpos($errors[0], 'Failed to log error') !== FALSE, 'The error handling logs when an error could not be logged to the logger.');
Chris@0 261
Chris@0 262 $expected_path = \Drupal::root() . '/core/modules/system/tests/modules/error_service_test/src/MonkeysInTheControlRoom.php';
Chris@0 263 $expected_line = 59;
Chris@0 264 $expected_entry = "Failed to log error: Exception: Deforestation in Drupal\\error_service_test\\MonkeysInTheControlRoom->handle() (line ${expected_line} of ${expected_path})";
Chris@0 265 $this->assert(strpos($errors[0], $expected_entry) !== FALSE, 'Original error logged to the PHP error log when an exception is thrown by a logger');
Chris@0 266
Chris@0 267 // The exception is expected. Do not interpret it as a test failure. Not
Chris@0 268 // using File API; a potential error must trigger a PHP warning.
Chris@0 269 unlink(\Drupal::root() . '/' . $this->siteDirectory . '/error.log');
Chris@0 270 }
Chris@0 271
Chris@0 272 /**
Chris@0 273 * {@inheritdoc}
Chris@0 274 */
Chris@0 275 protected function error($message = '', $group = 'Other', array $caller = NULL) {
Chris@0 276 if (!empty($this->expectedExceptionMessage) && strpos($message, $this->expectedExceptionMessage) !== FALSE) {
Chris@0 277 // We're expecting this error.
Chris@0 278 return FALSE;
Chris@0 279 }
Chris@0 280 return parent::error($message, $group, $caller);
Chris@0 281 }
Chris@0 282
Chris@0 283 }