Further Drupal 8.6.4 changes. Some core files were not committed before a commit...
[yaffs-website] / web / core / modules / simpletest / src / Tests / SimpleTestTest.php
1 <?php
2
3 namespace Drupal\simpletest\Tests;
4
5 use Drupal\Component\Utility\Crypt;
6 use Drupal\Core\Test\TestDatabase;
7 use Drupal\simpletest\WebTestBase;
8
9 /**
10  * Tests SimpleTest's web interface: check that the intended tests were run and
11  * ensure that test reports display the intended results. Also test SimpleTest's
12  * internal browser and APIs implicitly.
13  *
14  * @group simpletest
15  */
16 class SimpleTestTest extends WebTestBase {
17
18   /**
19    * Modules to enable.
20    *
21    * @var array
22    */
23   public static $modules = ['simpletest'];
24
25   /**
26    * The results array that has been parsed by getTestResults().
27    *
28    * @var array
29    */
30   protected $childTestResults;
31
32   /**
33    * Stores the test ID from each test run for comparison.
34    *
35    * Used to ensure they are incrementing.
36    */
37   protected $testIds = [];
38
39   /**
40    * Translated fail message.
41    *
42    * @var string
43    */
44   private $failMessage = '';
45
46   /**
47    * Translated pass message.
48    * @var string
49    */
50   private $passMessage = '';
51
52   /**
53    * A valid and recognized permission.
54    *
55    * @var string
56    */
57   protected $validPermission;
58
59   /**
60    * An invalid or unrecognized permission.
61    *
62    * @var string
63    */
64   protected $invalidPermission;
65
66   protected function setUp() {
67     if (!$this->isInChildSite()) {
68       $php = <<<'EOD'
69 <?php
70
71 # Make sure that the $test_class variable is defined when this file is included.
72 if ($test_class) {
73 }
74
75 # Define a function to be able to check that this file was loaded with
76 # function_exists().
77 if (!function_exists('simpletest_test_stub_settings_function')) {
78   function simpletest_test_stub_settings_function() {}
79 }
80 EOD;
81
82       file_put_contents($this->siteDirectory . '/' . 'settings.testing.php', $php);
83       // @see \Drupal\system\Tests\DrupalKernel\DrupalKernelSiteTest
84       $class = __CLASS__;
85       $yaml = <<<EOD
86 services:
87   # Add a new service.
88   site.service.yml:
89     class: $class
90   # Swap out a core service.
91   cache.backend.database:
92     class: Drupal\Core\Cache\MemoryBackendFactory
93 EOD;
94       file_put_contents($this->siteDirectory . '/testing.services.yml', $yaml);
95
96       $original_container = $this->originalContainer;
97       parent::setUp();
98       $this->assertNotIdentical(\Drupal::getContainer(), $original_container, 'WebTestBase test creates a new container.');
99       // Create and log in an admin user.
100       $this->drupalLogin($this->drupalCreateUser(['administer unit tests']));
101     }
102     else {
103       // This causes three of the five fails that are asserted in
104       // confirmStubResults().
105       self::$modules = ['non_existent_module'];
106       parent::setUp();
107     }
108   }
109
110   /**
111    * Ensures the tests selected through the web interface are run and displayed.
112    */
113   public function testWebTestRunner() {
114     $this->passMessage = t('SimpleTest pass.');
115     $this->failMessage = t('SimpleTest fail.');
116     $this->validPermission = 'access administration pages';
117     $this->invalidPermission = 'invalid permission';
118
119     if ($this->isInChildSite()) {
120       // Only run following code if this test is running itself through a CURL
121       // request.
122       $this->stubTest();
123     }
124     else {
125       // Run twice so test_ids can be accumulated.
126       for ($i = 0; $i < 2; $i++) {
127         // Run this test from web interface.
128         $this->drupalGet('admin/config/development/testing');
129
130         $edit = [];
131         $edit['tests[Drupal\simpletest\Tests\SimpleTestTest]'] = TRUE;
132         $this->drupalPostForm(NULL, $edit, t('Run tests'));
133
134         // Parse results and confirm that they are correct.
135         $this->getTestResults();
136         $this->confirmStubTestResults();
137       }
138
139       // Regression test for #290316.
140       // Check that test_id is incrementing.
141       $this->assertTrue($this->testIds[0] != $this->testIds[1], 'Test ID is incrementing.');
142     }
143   }
144
145   /**
146    * Test to be run and the results confirmed.
147    *
148    * Here we force test results which must match the expected results from
149    * confirmStubResults().
150    */
151   public function stubTest() {
152     // Ensure the .htkey file exists since this is only created just before a
153     // request. This allows the stub test to make requests. The event does not
154     // fire here and drupal_generate_test_ua() can not generate a key for a
155     // test in a test since the prefix has changed.
156     // @see \Drupal\Core\Test\HttpClientMiddleware\TestHttpClientMiddleware::onBeforeSendRequest()
157     // @see drupal_generate_test_ua();
158     $test_db = new TestDatabase($this->databasePrefix);
159     $key_file = DRUPAL_ROOT . '/' . $test_db->getTestSitePath() . '/.htkey';
160     $private_key = Crypt::randomBytesBase64(55);
161     $site_path = $this->container->get('site.path');
162     file_put_contents($key_file, $private_key);
163
164     // Check to see if runtime assertions are indeed on, if successful this
165     // will be the first of sixteen passes asserted in confirmStubResults()
166     try {
167       // Test with minimum possible arguments to make sure no notice for
168       // missing argument is thrown.
169       assert(FALSE);
170       $this->fail('Runtime assertions are not working.');
171     }
172     catch (\AssertionError $e) {
173       try {
174         // Now test with an error message to ensure it is correctly passed
175         // along by the rethrow.
176         assert(FALSE, 'Lorem Ipsum');
177       }
178       catch (\AssertionError $e) {
179         $this->assertEqual($e->getMessage(), 'Lorem Ipsum', 'Runtime assertions Enabled and running.');
180       }
181     }
182     // This causes the second of the sixteen passes asserted in
183     // confirmStubResults().
184     $this->pass($this->passMessage);
185
186     // The first three fails are caused by enabling a non-existent module in
187     // setUp().
188
189     // This causes the fourth of the five fails asserted in
190     // confirmStubResults().
191     $this->fail($this->failMessage);
192
193     // This causes the third to fifth of the sixteen passes asserted in
194     // confirmStubResults().
195     $user = $this->drupalCreateUser([$this->validPermission], 'SimpleTestTest');
196
197     // This causes the fifth of the five fails asserted in confirmStubResults().
198     $this->drupalCreateUser([$this->invalidPermission]);
199
200     // Test logging in as a user.
201     // This causes the sixth to tenth of the sixteen passes asserted in
202     // confirmStubResults().
203     $this->drupalLogin($user);
204
205     // This causes the eleventh of the sixteen passes asserted in
206     // confirmStubResults().
207     $this->pass(t('Test ID is @id.', ['@id' => $this->testId]));
208
209     // These cause the twelfth to fifteenth of the sixteen passes asserted in
210     // confirmStubResults().
211     $this->assertTrue(file_exists($site_path . '/settings.testing.php'));
212     // Check the settings.testing.php file got included.
213     $this->assertTrue(function_exists('simpletest_test_stub_settings_function'));
214     // Check that the test-specific service file got loaded.
215     $this->assertTrue($this->container->has('site.service.yml'));
216     $this->assertIdentical(get_class($this->container->get('cache.backend.database')), 'Drupal\Core\Cache\MemoryBackendFactory');
217
218     // These cause the two exceptions asserted in confirmStubResults().
219     // Call trigger_error() without the required argument to trigger an E_WARNING.
220     trigger_error();
221     // Generates a warning inside a PHP function.
222     array_key_exists(NULL, NULL);
223
224     // This causes the sixteenth of the sixteen passes asserted in
225     // confirmStubResults().
226     $this->assertNothing();
227
228     // This causes the debug message asserted in confirmStubResults().
229     debug('Foo', 'Debug', FALSE);
230   }
231
232   /**
233    * Assert nothing.
234    */
235   public function assertNothing() {
236     $this->pass("This is nothing.");
237   }
238
239   /**
240    * Confirm that the stub test produced the desired results.
241    */
242   public function confirmStubTestResults() {
243     $this->assertAssertion(t('Unable to install modules %modules due to missing modules %missing.', ['%modules' => 'non_existent_module', '%missing' => 'non_existent_module']), 'Other', 'Fail', 'SimpleTestTest.php', 'Drupal\simpletest\Tests\SimpleTestTest->setUp()');
244
245     $this->assertAssertion($this->passMessage, 'Other', 'Pass', 'SimpleTestTest.php', 'Drupal\simpletest\Tests\SimpleTestTest->stubTest()');
246     $this->assertAssertion($this->failMessage, 'Other', 'Fail', 'SimpleTestTest.php', 'Drupal\simpletest\Tests\SimpleTestTest->stubTest()');
247
248     $this->assertAssertion(t('Created permissions: @perms', ['@perms' => $this->validPermission]), 'Role', 'Pass', 'SimpleTestTest.php', 'Drupal\simpletest\Tests\SimpleTestTest->stubTest()');
249     $this->assertAssertion(t('Invalid permission %permission.', ['%permission' => $this->invalidPermission]), 'Role', 'Fail', 'SimpleTestTest.php', 'Drupal\simpletest\Tests\SimpleTestTest->stubTest()');
250
251     // Check that the user was logged in successfully.
252     $this->assertAssertion('User SimpleTestTest successfully logged in.', 'User login', 'Pass', 'SimpleTestTest.php', 'Drupal\simpletest\Tests\SimpleTestTest->stubTest()');
253
254     // Check that a warning is caught by simpletest. The exact error message
255     // differs between PHP versions so only the function name is checked.
256     $this->assertAssertion('trigger_error()', 'Warning', 'Fail', 'SimpleTestTest.php', 'Drupal\simpletest\Tests\SimpleTestTest->stubTest()');
257
258     // Check that the backtracing code works for specific assert function.
259     $this->assertAssertion('This is nothing.', 'Other', 'Pass', 'SimpleTestTest.php', 'Drupal\simpletest\Tests\SimpleTestTest->stubTest()');
260
261     // Check that errors that occur inside PHP internal functions are correctly
262     // reported. The exact error message differs between PHP versions so we
263     // check only the function name 'array_key_exists'.
264     $this->assertAssertion('array_key_exists', 'Warning', 'Fail', 'SimpleTestTest.php', 'Drupal\simpletest\Tests\SimpleTestTest->stubTest()');
265
266     $this->assertAssertion("Debug: 'Foo'", 'Debug', 'Fail', 'SimpleTestTest.php', 'Drupal\simpletest\Tests\SimpleTestTest->stubTest()');
267
268     $this->assertEqual('16 passes, 3 fails, 2 exceptions, 3 debug messages', $this->childTestResults['summary']);
269
270     $this->testIds[] = $test_id = $this->getTestIdFromResults();
271     $this->assertTrue($test_id, 'Found test ID in results.');
272   }
273
274   /**
275    * Fetch the test id from the test results.
276    */
277   public function getTestIdFromResults() {
278     foreach ($this->childTestResults['assertions'] as $assertion) {
279       if (preg_match('@^Test ID is ([0-9]*)\.$@', $assertion['message'], $matches)) {
280         return $matches[1];
281       }
282     }
283     return NULL;
284   }
285
286   /**
287    * Asserts that an assertion with specified values is displayed in results.
288    *
289    * @param string $message
290    *   Assertion message.
291    * @param string $type
292    *   Assertion type.
293    * @param string $status
294    *   Assertion status.
295    * @param string $file
296    *   File where the assertion originated.
297    * @param string $function
298    *   Function where the assertion originated.
299    *
300    * @return Assertion result.
301    */
302   public function assertAssertion($message, $type, $status, $file, $function) {
303     $message = trim(strip_tags($message));
304     $found = FALSE;
305     foreach ($this->childTestResults['assertions'] as $assertion) {
306       if ((strpos($assertion['message'], $message) !== FALSE) &&
307           $assertion['type'] == $type &&
308           $assertion['status'] == $status &&
309           $assertion['file'] == $file &&
310           $assertion['function'] == $function) {
311         $found = TRUE;
312         break;
313       }
314     }
315     return $this->assertTrue($found, format_string('Found assertion {"@message", "@type", "@status", "@file", "@function"}.', ['@message' => $message, '@type' => $type, '@status' => $status, "@file" => $file, "@function" => $function]));
316   }
317
318   /**
319    * Get the results from a test and store them in the class array $results.
320    */
321   public function getTestResults() {
322     $results = [];
323     if ($this->parse()) {
324       if ($details = $this->getResultFieldSet()) {
325         // Code assumes this is the only test in group.
326         $results['summary'] = $this->asText($details->div->div[1]);
327         $results['name'] = $this->asText($details->summary);
328
329         $results['assertions'] = [];
330         $tbody = $details->div->table->tbody;
331         foreach ($tbody->tr as $row) {
332           $assertion = [];
333           $assertion['message'] = $this->asText($row->td[0]);
334           $assertion['type'] = $this->asText($row->td[1]);
335           $assertion['file'] = $this->asText($row->td[2]);
336           $assertion['line'] = $this->asText($row->td[3]);
337           $assertion['function'] = $this->asText($row->td[4]);
338           $ok_url = file_url_transform_relative(file_create_url('core/misc/icons/73b355/check.svg'));
339           $assertion['status'] = ($row->td[5]->img['src'] == $ok_url) ? 'Pass' : 'Fail';
340           $results['assertions'][] = $assertion;
341         }
342       }
343     }
344     $this->childTestResults = $results;
345   }
346
347   /**
348    * Get the details containing the results for group this test is in.
349    */
350   public function getResultFieldSet() {
351     $all_details = $this->xpath('//details');
352     foreach ($all_details as $details) {
353       if ($this->asText($details->summary) == __CLASS__) {
354         return $details;
355       }
356     }
357     return FALSE;
358   }
359
360   /**
361    * Extract the text contained by the element.
362    *
363    * @param $element
364    *   Element to extract text from.
365    *
366    * @return
367    *   Extracted text.
368    */
369   public function asText(\SimpleXMLElement $element) {
370     if (!is_object($element)) {
371       return $this->fail('The element is not an element.');
372     }
373     return trim(html_entity_decode(strip_tags($element->asXML())));
374   }
375
376 }