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