comparison core/modules/system/src/Tests/System/UncaughtExceptionTest.php @ 0:4c8ae668cc8c

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