More updates to stop using dev or alpha or beta versions.
[yaffs-website] / web / core / modules / system / src / Tests / System / UncaughtExceptionTest.php
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     $this->assertRaw('The website encountered an unexpected error');
203     $this->assertRaw($this->expectedExceptionMessage);
204     $this->assertErrorLogged($this->expectedExceptionMessage);
205   }
206
207   /**
208    * Tests the case when the database connection is gone.
209    */
210   public function testLostDatabaseConnection() {
211     $incorrect_username = $this->randomMachineName(16);
212     switch ($this->container->get('database')->driver()) {
213       case 'pgsql':
214       case 'mysql':
215         $this->expectedExceptionMessage = $incorrect_username;
216         break;
217       default:
218         // We can not carry out this test.
219         $this->pass('Unable to run \Drupal\system\Tests\System\UncaughtExceptionTest::testLostDatabaseConnection for this database type.');
220         return;
221     }
222
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,
227       'required' => TRUE,
228     ];
229     $settings['databases']['default']['default']['password'] = (object) [
230       'value' => $this->randomMachineName(16),
231       'required' => TRUE,
232     ];
233
234     $this->writeSettings($settings);
235
236     $this->drupalGet('');
237     $this->assertResponse(500);
238     $this->assertRaw('DatabaseAccessDeniedException');
239     $this->assertErrorLogged($this->expectedExceptionMessage);
240   }
241
242   /**
243    * Tests fallback to PHP error log when an exception is thrown while logging.
244    */
245   public function testLoggerException() {
246     // Ensure the test error log is empty before these tests.
247     $this->assertNoErrorsLogged();
248
249     $this->expectedExceptionMessage = 'Deforestation';
250     \Drupal::state()->set('error_service_test.break_logger', TRUE);
251
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);
256
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.');
261
262     $expected_path = \Drupal::root() . '/core/modules/system/tests/modules/error_service_test/src/MonkeysInTheControlRoom.php';
263     $expected_line = 59;
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');
266
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');
270   }
271
272   /**
273    * {@inheritdoc}
274    */
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.
278       return FALSE;
279     }
280     return parent::error($message, $group, $caller);
281   }
282
283 }