X-Git-Url: http://www.aleph1.co.uk/gitweb/?a=blobdiff_plain;f=web%2Fcore%2Fmodules%2Fsystem%2Fsrc%2FTests%2FSystem%2FUncaughtExceptionTest.php;fp=web%2Fcore%2Fmodules%2Fsystem%2Fsrc%2FTests%2FSystem%2FUncaughtExceptionTest.php;h=b66837e1c7bd2eda1e7fe44de16258d0b4a2a10b;hb=a2bd1bf0c2c1f1a17d188f4dc0726a45494cefae;hp=0000000000000000000000000000000000000000;hpb=57c063afa3f66b07c4bbddc2d6129a96d90f0aad;p=yaffs-website diff --git a/web/core/modules/system/src/Tests/System/UncaughtExceptionTest.php b/web/core/modules/system/src/Tests/System/UncaughtExceptionTest.php new file mode 100644 index 000000000..b66837e1c --- /dev/null +++ b/web/core/modules/system/src/Tests/System/UncaughtExceptionTest.php @@ -0,0 +1,285 @@ +siteDirectory . '/settings.php'; + chmod($settings_filename, 0777); + $settings_php = file_get_contents($settings_filename); + $settings_php .= "\ninclude_once 'core/modules/system/src/Tests/Bootstrap/ErrorContainer.php';\n"; + $settings_php .= "\ninclude_once 'core/modules/system/src/Tests/Bootstrap/ExceptionContainer.php';\n"; + file_put_contents($settings_filename, $settings_php); + + $settings = []; + $settings['config']['system.logging']['error_level'] = (object) [ + 'value' => ERROR_REPORTING_DISPLAY_VERBOSE, + 'required' => TRUE, + ]; + $this->writeSettings($settings); + } + + /** + * Tests uncaught exception handling when system is in a bad state. + */ + public function testUncaughtException() { + $this->expectedExceptionMessage = 'Oh oh, bananas in the instruments.'; + \Drupal::state()->set('error_service_test.break_bare_html_renderer', TRUE); + + $this->config('system.logging') + ->set('error_level', ERROR_REPORTING_HIDE) + ->save(); + $settings = []; + $settings['config']['system.logging']['error_level'] = (object) [ + 'value' => ERROR_REPORTING_HIDE, + 'required' => TRUE, + ]; + $this->writeSettings($settings); + + $this->drupalGet(''); + $this->assertResponse(500); + $this->assertText('The website encountered an unexpected error. Please try again later.'); + $this->assertNoText($this->expectedExceptionMessage); + + $this->config('system.logging') + ->set('error_level', ERROR_REPORTING_DISPLAY_ALL) + ->save(); + $settings = []; + $settings['config']['system.logging']['error_level'] = (object) [ + 'value' => ERROR_REPORTING_DISPLAY_ALL, + 'required' => TRUE, + ]; + $this->writeSettings($settings); + + $this->drupalGet(''); + $this->assertResponse(500); + $this->assertText('The website encountered an unexpected error. Please try again later.'); + $this->assertText($this->expectedExceptionMessage); + $this->assertErrorLogged($this->expectedExceptionMessage); + } + + /** + * Tests uncaught exception handling with custom exception handler. + */ + public function testUncaughtExceptionCustomExceptionHandler() { + $settings_filename = $this->siteDirectory . '/settings.php'; + chmod($settings_filename, 0777); + $settings_php = file_get_contents($settings_filename); + $settings_php .= "\n"; + $settings_php .= "set_exception_handler(function() {\n"; + $settings_php .= " header('HTTP/1.1 418 I\'m a teapot');\n"; + $settings_php .= " print('Oh oh, flying teapots');\n"; + $settings_php .= "});\n"; + file_put_contents($settings_filename, $settings_php); + + \Drupal::state()->set('error_service_test.break_bare_html_renderer', TRUE); + + $this->drupalGet(''); + $this->assertResponse(418); + $this->assertNoText('The website encountered an unexpected error. Please try again later.'); + $this->assertNoText('Oh oh, bananas in the instruments'); + $this->assertText('Oh oh, flying teapots'); + } + + /** + * Tests a missing dependency on a service. + */ + public function testMissingDependency() { + if (version_compare(PHP_VERSION, '7.1') < 0) { + $this->expectedExceptionMessage = 'Argument 1 passed to Drupal\error_service_test\LonelyMonkeyClass::__construct() must be an instance of Drupal\Core\Database\Connection, non'; + } + else { + $this->expectedExceptionMessage = 'Too few arguments to function Drupal\error_service_test\LonelyMonkeyClass::__construct(), 0 passed'; + } + $this->drupalGet('broken-service-class'); + $this->assertResponse(500); + + $this->assertRaw('The website encountered an unexpected error.'); + $this->assertRaw($this->expectedExceptionMessage); + $this->assertErrorLogged($this->expectedExceptionMessage); + } + + /** + * Tests a missing dependency on a service with a custom error handler. + */ + public function testMissingDependencyCustomErrorHandler() { + $settings_filename = $this->siteDirectory . '/settings.php'; + chmod($settings_filename, 0777); + $settings_php = file_get_contents($settings_filename); + $settings_php .= "\n"; + $settings_php .= "set_error_handler(function() {\n"; + $settings_php .= " header('HTTP/1.1 418 I\'m a teapot');\n"; + $settings_php .= " print('Oh oh, flying teapots');\n"; + $settings_php .= " exit();\n"; + $settings_php .= "});\n"; + $settings_php .= "\$settings['teapots'] = TRUE;\n"; + file_put_contents($settings_filename, $settings_php); + + $this->drupalGet('broken-service-class'); + $this->assertResponse(418); + $this->assertRaw('Oh oh, flying teapots'); + + $message = 'Argument 1 passed to Drupal\error_service_test\LonelyMonkeyClass::__construct() must be an instance of Drupal\Core\Database\Connection, non'; + + $this->assertNoRaw('The website encountered an unexpected error.'); + $this->assertNoRaw($message); + + $found_exception = FALSE; + foreach ($this->assertions as &$assertion) { + if (strpos($assertion['message'], $message) !== FALSE) { + $found_exception = TRUE; + $this->deleteAssert($assertion['message_id']); + unset($assertion); + } + } + + $this->assertTrue($found_exception, 'Ensure that the exception of a missing constructor argument was triggered.'); + } + + /** + * Tests a container which has an error. + */ + public function testErrorContainer() { + $settings = []; + $settings['settings']['container_base_class'] = (object) [ + 'value' => '\Drupal\system\Tests\Bootstrap\ErrorContainer', + 'required' => TRUE, + ]; + $this->writeSettings($settings); + \Drupal::service('kernel')->invalidateContainer(); + + $this->expectedExceptionMessage = 'Argument 1 passed to Drupal\system\Tests\Bootstrap\ErrorContainer::Drupal\system\Tests\Bootstrap\{closur'; + $this->drupalGet(''); + $this->assertResponse(500); + + $this->assertRaw($this->expectedExceptionMessage); + $this->assertErrorLogged($this->expectedExceptionMessage); + } + + /** + * Tests a container which has an exception really early. + */ + public function testExceptionContainer() { + $settings = []; + $settings['settings']['container_base_class'] = (object) [ + 'value' => '\Drupal\system\Tests\Bootstrap\ExceptionContainer', + 'required' => TRUE, + ]; + $this->writeSettings($settings); + \Drupal::service('kernel')->invalidateContainer(); + + $this->expectedExceptionMessage = 'Thrown exception during Container::get'; + $this->drupalGet(''); + $this->assertResponse(500); + + + $this->assertRaw('The website encountered an unexpected error'); + $this->assertRaw($this->expectedExceptionMessage); + $this->assertErrorLogged($this->expectedExceptionMessage); + } + + /** + * Tests the case when the database connection is gone. + */ + public function testLostDatabaseConnection() { + $incorrect_username = $this->randomMachineName(16); + switch ($this->container->get('database')->driver()) { + case 'pgsql': + case 'mysql': + $this->expectedExceptionMessage = $incorrect_username; + break; + default: + // We can not carry out this test. + $this->pass('Unable to run \Drupal\system\Tests\System\UncaughtExceptionTest::testLostDatabaseConnection for this database type.'); + return; + } + + // We simulate a broken database connection by rewrite settings.php to no + // longer have the proper data. + $settings['databases']['default']['default']['username'] = (object) [ + 'value' => $incorrect_username, + 'required' => TRUE, + ]; + $settings['databases']['default']['default']['password'] = (object) [ + 'value' => $this->randomMachineName(16), + 'required' => TRUE, + ]; + + $this->writeSettings($settings); + + $this->drupalGet(''); + $this->assertResponse(500); + $this->assertRaw('DatabaseAccessDeniedException'); + $this->assertErrorLogged($this->expectedExceptionMessage); + } + + /** + * Tests fallback to PHP error log when an exception is thrown while logging. + */ + public function testLoggerException() { + // Ensure the test error log is empty before these tests. + $this->assertNoErrorsLogged(); + + $this->expectedExceptionMessage = 'Deforestation'; + \Drupal::state()->set('error_service_test.break_logger', TRUE); + + $this->drupalGet(''); + $this->assertResponse(500); + $this->assertText('The website encountered an unexpected error. Please try again later.'); + $this->assertRaw($this->expectedExceptionMessage); + + // Find fatal error logged to the simpletest error.log + $errors = file(\Drupal::root() . '/' . $this->siteDirectory . '/error.log'); + $this->assertIdentical(count($errors), 8, 'The error + the error that the logging service is broken has been written to the error log.'); + $this->assertTrue(strpos($errors[0], 'Failed to log error') !== FALSE, 'The error handling logs when an error could not be logged to the logger.'); + + $expected_path = \Drupal::root() . '/core/modules/system/tests/modules/error_service_test/src/MonkeysInTheControlRoom.php'; + $expected_line = 59; + $expected_entry = "Failed to log error: Exception: Deforestation in Drupal\\error_service_test\\MonkeysInTheControlRoom->handle() (line ${expected_line} of ${expected_path})"; + $this->assert(strpos($errors[0], $expected_entry) !== FALSE, 'Original error logged to the PHP error log when an exception is thrown by a logger'); + + // The exception is expected. Do not interpret it as a test failure. Not + // using File API; a potential error must trigger a PHP warning. + unlink(\Drupal::root() . '/' . $this->siteDirectory . '/error.log'); + } + + /** + * {@inheritdoc} + */ + protected function error($message = '', $group = 'Other', array $caller = NULL) { + if (!empty($this->expectedExceptionMessage) && strpos($message, $this->expectedExceptionMessage) !== FALSE) { + // We're expecting this error. + return FALSE; + } + return parent::error($message, $group, $caller); + } + +}