3 namespace Drupal\system\Tests\System;
5 use Drupal\simpletest\WebTestBase;
8 * Tests kernel panic when things are really messed up.
12 class UncaughtExceptionTest extends WebTestBase {
15 * Exceptions thrown by site under test that contain this text are ignored.
19 protected $expectedExceptionMessage;
26 public static $modules = ['error_service_test'];
31 protected function setUp() {
34 $settings_filename = $this->siteDirectory . '/settings.php';
35 chmod($settings_filename, 0777);
36 $settings_php = file_get_contents($settings_filename);
37 $settings_php .= "\ninclude_once 'core/modules/system/src/Tests/Bootstrap/ErrorContainer.php';\n";
38 $settings_php .= "\ninclude_once 'core/modules/system/src/Tests/Bootstrap/ExceptionContainer.php';\n";
39 file_put_contents($settings_filename, $settings_php);
42 $settings['config']['system.logging']['error_level'] = (object) [
43 'value' => ERROR_REPORTING_DISPLAY_VERBOSE,
46 $this->writeSettings($settings);
50 * Tests uncaught exception handling when system is in a bad state.
52 public function testUncaughtException() {
53 $this->expectedExceptionMessage = 'Oh oh, bananas in the instruments.';
54 \Drupal::state()->set('error_service_test.break_bare_html_renderer', TRUE);
56 $this->config('system.logging')
57 ->set('error_level', ERROR_REPORTING_HIDE)
60 $settings['config']['system.logging']['error_level'] = (object) [
61 'value' => ERROR_REPORTING_HIDE,
64 $this->writeSettings($settings);
67 $this->assertResponse(500);
68 $this->assertText('The website encountered an unexpected error. Please try again later.');
69 $this->assertNoText($this->expectedExceptionMessage);
71 $this->config('system.logging')
72 ->set('error_level', ERROR_REPORTING_DISPLAY_ALL)
75 $settings['config']['system.logging']['error_level'] = (object) [
76 'value' => ERROR_REPORTING_DISPLAY_ALL,
79 $this->writeSettings($settings);
82 $this->assertResponse(500);
83 $this->assertText('The website encountered an unexpected error. Please try again later.');
84 $this->assertText($this->expectedExceptionMessage);
85 $this->assertErrorLogged($this->expectedExceptionMessage);
89 * Tests uncaught exception handling with custom exception handler.
91 public function testUncaughtExceptionCustomExceptionHandler() {
92 $settings_filename = $this->siteDirectory . '/settings.php';
93 chmod($settings_filename, 0777);
94 $settings_php = file_get_contents($settings_filename);
95 $settings_php .= "\n";
96 $settings_php .= "set_exception_handler(function() {\n";
97 $settings_php .= " header('HTTP/1.1 418 I\'m a teapot');\n";
98 $settings_php .= " print('Oh oh, flying teapots');\n";
99 $settings_php .= "});\n";
100 file_put_contents($settings_filename, $settings_php);
102 \Drupal::state()->set('error_service_test.break_bare_html_renderer', TRUE);
104 $this->drupalGet('');
105 $this->assertResponse(418);
106 $this->assertNoText('The website encountered an unexpected error. Please try again later.');
107 $this->assertNoText('Oh oh, bananas in the instruments');
108 $this->assertText('Oh oh, flying teapots');
112 * Tests a missing dependency on a service.
114 public function testMissingDependency() {
115 if (version_compare(PHP_VERSION, '7.1') < 0) {
116 $this->expectedExceptionMessage = 'Argument 1 passed to Drupal\error_service_test\LonelyMonkeyClass::__construct() must be an instance of Drupal\Core\Database\Connection, non';
119 $this->expectedExceptionMessage = 'Too few arguments to function Drupal\error_service_test\LonelyMonkeyClass::__construct(), 0 passed';
121 $this->drupalGet('broken-service-class');
122 $this->assertResponse(500);
124 $this->assertRaw('The website encountered an unexpected error.');
125 $this->assertRaw($this->expectedExceptionMessage);
126 $this->assertErrorLogged($this->expectedExceptionMessage);
130 * Tests a missing dependency on a service with a custom error handler.
132 public function testMissingDependencyCustomErrorHandler() {
133 $settings_filename = $this->siteDirectory . '/settings.php';
134 chmod($settings_filename, 0777);
135 $settings_php = file_get_contents($settings_filename);
136 $settings_php .= "\n";
137 $settings_php .= "set_error_handler(function() {\n";
138 $settings_php .= " header('HTTP/1.1 418 I\'m a teapot');\n";
139 $settings_php .= " print('Oh oh, flying teapots');\n";
140 $settings_php .= " exit();\n";
141 $settings_php .= "});\n";
142 $settings_php .= "\$settings['teapots'] = TRUE;\n";
143 file_put_contents($settings_filename, $settings_php);
145 $this->drupalGet('broken-service-class');
146 $this->assertResponse(418);
147 $this->assertRaw('Oh oh, flying teapots');
149 $message = 'Argument 1 passed to Drupal\error_service_test\LonelyMonkeyClass::__construct() must be an instance of Drupal\Core\Database\Connection, non';
151 $this->assertNoRaw('The website encountered an unexpected error.');
152 $this->assertNoRaw($message);
154 $found_exception = FALSE;
155 foreach ($this->assertions as &$assertion) {
156 if (strpos($assertion['message'], $message) !== FALSE) {
157 $found_exception = TRUE;
158 $this->deleteAssert($assertion['message_id']);
163 $this->assertTrue($found_exception, 'Ensure that the exception of a missing constructor argument was triggered.');
167 * Tests a container which has an error.
169 public function testErrorContainer() {
171 $settings['settings']['container_base_class'] = (object) [
172 'value' => '\Drupal\system\Tests\Bootstrap\ErrorContainer',
175 $this->writeSettings($settings);
176 \Drupal::service('kernel')->invalidateContainer();
178 $this->expectedExceptionMessage = 'Argument 1 passed to Drupal\system\Tests\Bootstrap\ErrorContainer::Drupal\system\Tests\Bootstrap\{closur';
179 $this->drupalGet('');
180 $this->assertResponse(500);
182 $this->assertRaw($this->expectedExceptionMessage);
183 $this->assertErrorLogged($this->expectedExceptionMessage);
187 * Tests a container which has an exception really early.
189 public function testExceptionContainer() {
191 $settings['settings']['container_base_class'] = (object) [
192 'value' => '\Drupal\system\Tests\Bootstrap\ExceptionContainer',
195 $this->writeSettings($settings);
196 \Drupal::service('kernel')->invalidateContainer();
198 $this->expectedExceptionMessage = 'Thrown exception during Container::get';
199 $this->drupalGet('');
200 $this->assertResponse(500);
202 $this->assertRaw('The website encountered an unexpected error');
203 $this->assertRaw($this->expectedExceptionMessage);
204 $this->assertErrorLogged($this->expectedExceptionMessage);
208 * Tests the case when the database connection is gone.
210 public function testLostDatabaseConnection() {
211 $incorrect_username = $this->randomMachineName(16);
212 switch ($this->container->get('database')->driver()) {
215 $this->expectedExceptionMessage = $incorrect_username;
218 // We can not carry out this test.
219 $this->pass('Unable to run \Drupal\system\Tests\System\UncaughtExceptionTest::testLostDatabaseConnection for this database type.');
223 // We simulate a broken database connection by rewrite settings.php to no
224 // longer have the proper data.
225 $settings['databases']['default']['default']['username'] = (object) [
226 'value' => $incorrect_username,
229 $settings['databases']['default']['default']['password'] = (object) [
230 'value' => $this->randomMachineName(16),
234 $this->writeSettings($settings);
236 $this->drupalGet('');
237 $this->assertResponse(500);
238 $this->assertRaw('DatabaseAccessDeniedException');
239 $this->assertErrorLogged($this->expectedExceptionMessage);
243 * Tests fallback to PHP error log when an exception is thrown while logging.
245 public function testLoggerException() {
246 // Ensure the test error log is empty before these tests.
247 $this->assertNoErrorsLogged();
249 $this->expectedExceptionMessage = 'Deforestation';
250 \Drupal::state()->set('error_service_test.break_logger', TRUE);
252 $this->drupalGet('');
253 $this->assertResponse(500);
254 $this->assertText('The website encountered an unexpected error. Please try again later.');
255 $this->assertRaw($this->expectedExceptionMessage);
257 // Find fatal error logged to the simpletest error.log
258 $errors = file(\Drupal::root() . '/' . $this->siteDirectory . '/error.log');
259 $this->assertIdentical(count($errors), 8, 'The error + the error that the logging service is broken has been written to the error log.');
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.');
262 $expected_path = \Drupal::root() . '/core/modules/system/tests/modules/error_service_test/src/MonkeysInTheControlRoom.php';
264 $expected_entry = "Failed to log error: Exception: Deforestation in Drupal\\error_service_test\\MonkeysInTheControlRoom->handle() (line ${expected_line} of ${expected_path})";
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');
267 // The exception is expected. Do not interpret it as a test failure. Not
268 // using File API; a potential error must trigger a PHP warning.
269 unlink(\Drupal::root() . '/' . $this->siteDirectory . '/error.log');
275 protected function error($message = '', $group = 'Other', array $caller = NULL) {
276 if (!empty($this->expectedExceptionMessage) && strpos($message, $this->expectedExceptionMessage) !== FALSE) {
277 // We're expecting this error.
280 return parent::error($message, $group, $caller);