123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398 |
- <?php
- namespace Drupal\FunctionalTests\Bootstrap;
- use Drupal\Component\Render\FormattableMarkup;
- use Drupal\Tests\BrowserTestBase;
- /**
- * Tests kernel panic when things are really messed up.
- *
- * @group system
- */
- class UncaughtExceptionTest extends BrowserTestBase {
- /**
- * Last cURL response.
- *
- * @var string
- */
- protected $response = '';
- /**
- * Last cURL info.
- *
- * @var array
- */
- protected $info = [];
- /**
- * Exceptions thrown by site under test that contain this text are ignored.
- *
- * @var string
- */
- protected $expectedExceptionMessage;
- /**
- * Modules to enable.
- *
- * @var array
- */
- public static $modules = ['error_service_test', 'error_test'];
- /**
- * {@inheritdoc}
- */
- protected $defaultTheme = 'stark';
- /**
- * {@inheritdoc}
- */
- protected function setUp() {
- parent::setUp();
- $settings_filename = $this->siteDirectory . '/settings.php';
- chmod($settings_filename, 0777);
- $settings_php = file_get_contents($settings_filename);
- $settings_php .= "\ninclude_once 'core/tests/Drupal/FunctionalTests/Bootstrap/ErrorContainer.php';\n";
- $settings_php .= "\ninclude_once 'core/tests/Drupal/FunctionalTests/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 displaying an uncaught fatal error.
- */
- public function testUncaughtFatalError() {
- $fatal_error = [
- '%type' => 'TypeError',
- '@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',
- '%function' => 'Drupal\error_test\Controller\ErrorTestController->Drupal\error_test\Controller\{closure}()',
- ];
- $this->drupalGet('error-test/generate-fatals');
- $this->assertResponse(500);
- $message = new FormattableMarkup('%type: @message in %function (line ', $fatal_error);
- $this->assertRaw((string) $message);
- $this->assertRaw('<pre class="backtrace">');
- // Ensure we are escaping but not double escaping.
- $this->assertRaw(''');
- $this->assertNoRaw('&#039;');
- }
- /**
- * 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->assertSame('Oh oh, flying teapots', $this->response);
- }
- /**
- * Tests a container which has an error.
- */
- public function testErrorContainer() {
- $settings = [];
- $settings['settings']['container_base_class'] = (object) [
- 'value' => '\Drupal\FunctionalTests\Bootstrap\ErrorContainer',
- 'required' => TRUE,
- ];
- $this->writeSettings($settings);
- \Drupal::service('kernel')->invalidateContainer();
- $this->expectedExceptionMessage = 'Argument 1 passed to Drupal\FunctionalTests\Bootstrap\ErrorContainer::Drupal\FunctionalTests\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\FunctionalTests\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->markTestSkipped('Unable to run \Drupal\system\Tests\System\UncaughtExceptionTest::testLostDatabaseConnection for this database type.');
- }
- // 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 error.log
- $errors = file(\Drupal::root() . '/' . $this->siteDirectory . '/error.log');
- $this->assertCount(8, $errors, 'The error + the error that the logging service is broken has been written to the error log.');
- $this->assertStringContainsString('Failed to log error', $errors[0], '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->assertStringContainsString($expected_entry, $errors[0], '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');
- }
- /**
- * Asserts that a specific error has been logged to the PHP error log.
- *
- * @param string $error_message
- * The expected error message.
- *
- * @see \Drupal\simpletest\TestBase::prepareEnvironment()
- * @see \Drupal\Core\DrupalKernel::bootConfiguration()
- */
- protected function assertErrorLogged($error_message) {
- $error_log_filename = DRUPAL_ROOT . '/' . $this->siteDirectory . '/error.log';
- $this->assertFileExists($error_log_filename);
- $content = file_get_contents($error_log_filename);
- $rows = explode(PHP_EOL, $content);
- // We iterate over the rows in order to be able to remove the logged error
- // afterwards.
- $found = FALSE;
- foreach ($rows as $row_index => $row) {
- if (strpos($content, $error_message) !== FALSE) {
- $found = TRUE;
- unset($rows[$row_index]);
- }
- }
- file_put_contents($error_log_filename, implode("\n", $rows));
- $this->assertTrue($found, sprintf('The %s error message was logged.', $error_message));
- }
- /**
- * Asserts that no errors have been logged to the PHP error.log thus far.
- *
- * @see \Drupal\simpletest\TestBase::prepareEnvironment()
- * @see \Drupal\Core\DrupalKernel::bootConfiguration()
- */
- protected function assertNoErrorsLogged() {
- // Since PHP only creates the error.log file when an actual error is
- // triggered, it is sufficient to check whether the file exists.
- $this->assertFileNotExists(DRUPAL_ROOT . '/' . $this->siteDirectory . '/error.log');
- }
- /**
- * Retrieves a Drupal path or an absolute path.
- *
- * Executes a cURL request for processing errors and exceptions.
- *
- * @param string|\Drupal\Core\Url $path
- * Request path.
- * @param array $extra_options
- * (optional) Curl options to pass to curl_setopt()
- * @param array $headers
- * (optional) Not used.
- */
- protected function drupalGet($path, array $extra_options = [], array $headers = []) {
- $url = $this->buildUrl($path, ['absolute' => TRUE]);
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_URL, $url);
- curl_setopt($ch, CURLOPT_HEADER, FALSE);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
- curl_setopt($ch, CURLOPT_USERAGENT, drupal_generate_test_ua($this->databasePrefix));
- $this->response = curl_exec($ch);
- $this->info = curl_getinfo($ch);
- curl_close($ch);
- }
- /**
- * {@inheritdoc}
- */
- protected function assertResponse($code) {
- $this->assertSame($code, $this->info['http_code']);
- }
- /**
- * {@inheritdoc}
- */
- protected function assertText($text) {
- $this->assertStringContainsString($text, $this->response);
- }
- /**
- * {@inheritdoc}
- */
- protected function assertNoText($text) {
- $this->assertStringNotContainsString($text, $this->response);
- }
- /**
- * {@inheritdoc}
- */
- protected function assertRaw($text) {
- $this->assertText($text);
- }
- /**
- * {@inheritdoc}
- */
- protected function assertNoRaw($text) {
- $this->assertNoText($text);
- }
- }
|