TestBase.php 48.9 KB
Newer Older
1
2
3
4
<?php

namespace Drupal\simpletest;

5
use Drupal\Component\Assertion\Handle;
6
use Drupal\Component\Render\MarkupInterface;
7
use Drupal\Component\Utility\Crypt;
8
use Drupal\Component\Render\FormattableMarkup;
9
use Drupal\Core\Database\Database;
10
use Drupal\Core\Site\Settings;
11
use Drupal\Core\StreamWrapper\PublicStream;
12
use Drupal\Core\Test\TestDatabase;
13
use Drupal\Core\Test\TestSetupTrait;
14
use Drupal\Core\Utility\Error;
15
use Drupal\Tests\AssertHelperTrait as BaseAssertHelperTrait;
16
use Drupal\Tests\ConfigTestTrait;
17
18
use Drupal\Tests\RandomGeneratorTrait;
use Drupal\Tests\SessionTestTrait;
19
use Drupal\Tests\Traits\Core\GeneratePermutationsTrait;
20
21
22
23

/**
 * Base class for Drupal tests.
 *
24
 * Do not extend this class directly; use \Drupal\simpletest\WebTestBase.
25
26
 */
abstract class TestBase {
27

28
  use BaseAssertHelperTrait;
29
  use TestSetupTrait;
30
  use SessionTestTrait;
31
  use RandomGeneratorTrait;
32
  use GeneratePermutationsTrait;
33
34
35
36
37
  // For backwards compatibility switch the visbility of the methods to public.
  use ConfigTestTrait {
    configImporter as public;
    copyConfig as public;
  }
38

39
40
41
42
43
44
45
46
47
  /**
   * The database prefix of this test run.
   *
   * @var string
   */
  protected $databasePrefix = NULL;

  /**
   * Time limit for the test.
48
49
   *
   * @var int
50
51
52
53
54
55
56
57
   */
  protected $timeLimit = 500;

  /**
   * Current results of this test case.
   *
   * @var Array
   */
58
  public $results = [
59
60
61
62
    '#pass' => 0,
    '#fail' => 0,
    '#exception' => 0,
    '#debug' => 0,
63
  ];
64
65
66
67
68
69

  /**
   * Assertions thrown in that test case.
   *
   * @var Array
   */
70
  protected $assertions = [];
71
72
73
74
75
76
77
78
79

  /**
   * This class is skipped when looking for the source of an assertion.
   *
   * When displaying which function an assert comes from, it's not too useful
   * to see "WebTestBase->drupalLogin()', we would like to see the test
   * that called it. So we need to skip the classes defining these helper
   * methods.
   */
80
  protected $skipClasses = [__CLASS__ => TRUE];
81

82
83
84
  /**
   * TRUE if verbose debugging is enabled.
   *
85
   * @var bool
86
   */
87
  public $verbose;
88
89
90
91

  /**
   * Incrementing identifier for verbose output filenames.
   *
92
   * @var int
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
   */
  protected $verboseId = 0;

  /**
   * Safe class name for use in verbose output filenames.
   *
   * Namespaces separator (\) replaced with _.
   *
   * @var string
   */
  protected $verboseClassName;

  /**
   * Directory where verbose output files are put.
   *
   * @var string
   */
  protected $verboseDirectory;

112
113
114
115
116
117
118
119
120
121
122
123
  /**
   * URL to the verbose output file directory.
   *
   * @var string
   */
  protected $verboseDirectoryUrl;

  /**
   * The original configuration (variables), if available.
   *
   * @var string
   * @todo Remove all remnants of $GLOBALS['conf'].
124
   * @see https://www.drupal.org/node/2183323
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
   */
  protected $originalConf;

  /**
   * The original configuration (variables).
   *
   * @var string
   */
  protected $originalConfig;

  /**
   * The original configuration directories.
   *
   * An array of paths keyed by the CONFIG_*_DIRECTORY constants defined by
   * core/includes/bootstrap.inc.
   *
   * @var array
   */
  protected $originalConfigDirectories;

  /**
   * The original container.
   *
   * @var \Symfony\Component\DependencyInjection\ContainerInterface
   */
  protected $originalContainer;

  /**
   * The original file directory, before it was changed for testing purposes.
   *
   * @var string
   */
  protected $originalFileDirectory = NULL;

  /**
   * The original language.
   *
   * @var \Drupal\Core\Language\LanguageInterface
   */
  protected $originalLanguage;

166
167
168
169
170
171
172
  /**
   * The original database prefix when running inside Simpletest.
   *
   * @var string
   */
  protected $originalPrefix;

173
  /**
174
   * The name of the session cookie of the test-runner.
175
176
177
178
   *
   * @var string
   */
  protected $originalSessionName;
179

180
181
  /**
   * The settings array.
182
183
   *
   * @var array
184
185
186
   */
  protected $originalSettings;

187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
  /**
   * The original array of shutdown function callbacks.
   *
   * @var array
   */
  protected $originalShutdownCallbacks;

  /**
   * The original user, before testing began.
   *
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
  protected $originalUser;

  /**
   * The translation file directory for the test environment.
   *
   * This is set in TestBase::prepareEnvironment().
   *
   * @var string
   */
  protected $translationFilesDirectory;
209

210
  /**
211
212
   * Whether to die in case any test assertion fails.
   *
213
   * @var bool
214
215
   *
   * @see run-tests.sh
216
   */
217
  public $dieOnFail = FALSE;
218

219
220
221
222
223
224
225
  /**
   * The config importer that can used in a test.
   *
   * @var \Drupal\Core\Config\ConfigImporter
   */
  protected $configImporter;

226
227
228
229
  /**
   * HTTP authentication method (specified as a CURLAUTH_* constant).
   *
   * @var int
230
   * @see http://php.net/manual/function.curl-setopt.php
231
232
233
234
235
236
237
238
239
240
   */
  protected $httpAuthMethod = CURLAUTH_BASIC;

  /**
   * HTTP authentication credentials (<username>:<password>).
   *
   * @var string
   */
  protected $httpAuthCredentials = NULL;

241
242
243
244
245
246
247
248
249
250
  /**
   * Constructor for Test.
   *
   * @param $test_id
   *   Tests with the same id are reported together.
   */
  public function __construct($test_id = NULL) {
    $this->testId = $test_id;
  }

251
252
253
254
255
256
257
258
259
260
261
262
263
264
  /**
   * Fail the test if it belongs to a PHPUnit-based framework.
   *
   * This would probably be caused by automated test conversions such as those
   * in https://www.drupal.org/project/drupal/issues/2770921.
   */
  public function checkTestHierarchyMismatch() {
    // We can use getPhpunitTestSuite() because it uses a regex on the class'
    // namespace to deduce the PHPUnit test suite.
    if (TestDiscovery::getPhpunitTestSuite(get_class($this)) !== FALSE) {
      $this->fail(get_class($this) . ' incorrectly subclasses ' . __CLASS__ . ', it should probably extend \Drupal\Tests\BrowserTestBase instead.');
    }
  }

265
266
267
268
269
  /**
   * Performs setup tasks before each individual test method is run.
   */
  abstract protected function setUp();

270
271
272
273
274
275
276
  /**
   * Checks the matching requirements for Test.
   *
   * @return
   *   Array of errors containing a list of unmet requirements.
   */
  protected function checkRequirements() {
277
    return [];
278
279
  }

280
281
282
283
284
285
286
287
288
  /**
   * Helper method to store an assertion record in the configured database.
   *
   * This method decouples database access from assertion logic.
   *
   * @param array $assertion
   *   Keyed array representing an assertion, as generated by assert().
   *
   * @see self::assert()
289
290
291
   *
   * @return \Drupal\Core\Database\StatementInterface|int|null
   *   The message ID.
292
293
294
   */
  protected function storeAssertion(array $assertion) {
    return self::getDatabaseConnection()
295
      ->insert('simpletest', ['return' => Database::RETURN_INSERT_ID])
296
297
298
299
      ->fields($assertion)
      ->execute();
  }

300
301
302
303
  /**
   * Internal helper: stores the assert.
   *
   * @param $status
304
   *   Can be 'pass', 'fail', 'exception', 'debug'.
305
   *   TRUE is a synonym for 'pass', FALSE for 'fail'.
306
   * @param string|\Drupal\Component\Render\MarkupInterface $message
307
   *   (optional) A message to display with the assertion. Do not translate
308
   *   messages: use \Drupal\Component\Render\FormattableMarkup to embed
309
310
   *   variables in the message text, not t(). If left blank, a default message
   *   will be displayed.
311
   * @param $group
312
313
314
315
   *   (optional) The group this message is in, which is displayed in a column
   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
   *   translate this string. Defaults to 'Other'; most tests do not override
   *   this default.
316
317
318
319
320
321
322
323
   * @param $caller
   *   By default, the assert comes from a function whose name starts with
   *   'test'. Instead, you can specify where this assert originates from
   *   by passing in an associative array as $caller. Key 'file' is
   *   the name of the source file, 'line' is the line number and 'function'
   *   is the caller function itself.
   */
  protected function assert($status, $message = '', $group = 'Other', array $caller = NULL) {
324
    if ($message instanceof MarkupInterface) {
325
326
      $message = (string) $message;
    }
327
328
329
330
331
332
333
334
335
336
337
338
339
340
    // Convert boolean status to string status.
    if (is_bool($status)) {
      $status = $status ? 'pass' : 'fail';
    }

    // Increment summary result counter.
    $this->results['#' . $status]++;

    // Get the function information about the call to the assertion method.
    if (!$caller) {
      $caller = $this->getAssertionCall();
    }

    // Creation assertion array that can be displayed while tests are running.
341
    $assertion = [
342
343
344
345
346
347
348
349
      'test_id' => $this->testId,
      'test_class' => get_class($this),
      'status' => $status,
      'message' => $message,
      'message_group' => $group,
      'function' => $caller['function'],
      'line' => $caller['line'],
      'file' => $caller['file'],
350
    ];
351
352

    // Store assertion for display after the test has completed.
353
354
355
    $message_id = $this->storeAssertion($assertion);
    $assertion['message_id'] = $message_id;
    $this->assertions[] = $assertion;
356
357
358
359
360
361
362

    // We do not use a ternary operator here to allow a breakpoint on
    // test failure.
    if ($status == 'pass') {
      return TRUE;
    }
    else {
363
      if ($this->dieOnFail && ($status == 'fail' || $status == 'exception')) {
364
365
        exit(1);
      }
366
367
368
369
370
371
372
373
374
375
376
      return FALSE;
    }
  }

  /**
   * Store an assertion from outside the testing context.
   *
   * This is useful for inserting assertions that can only be recorded after
   * the test case has been destroyed, such as PHP fatal errors. The caller
   * information is not automatically gathered since the caller is most likely
   * inserting the assertion on behalf of other code. In all other respects
377
   * the method behaves just like \Drupal\simpletest\TestBase::assert() in terms
378
379
380
381
382
   * of storing the assertion.
   *
   * @return
   *   Message ID of the stored assertion.
   *
383
384
   * @see \Drupal\simpletest\TestBase::assert()
   * @see \Drupal\simpletest\TestBase::deleteAssert()
385
   */
386
  public static function insertAssert($test_id, $test_class, $status, $message = '', $group = 'Other', array $caller = []) {
387
388
389
390
391
    // Convert boolean status to string status.
    if (is_bool($status)) {
      $status = $status ? 'pass' : 'fail';
    }

392
    $caller += [
393
      'function' => 'Unknown',
394
      'line' => 0,
395
      'file' => 'Unknown',
396
    ];
397

398
    $assertion = [
399
400
401
402
403
404
405
406
      'test_id' => $test_id,
      'test_class' => $test_class,
      'status' => $status,
      'message' => $message,
      'message_group' => $group,
      'function' => $caller['function'],
      'line' => $caller['line'],
      'file' => $caller['file'],
407
    ];
408

409
    // We can't use storeAssertion() because this method is static.
410
411
    return self::getDatabaseConnection()
      ->insert('simpletest')
412
413
414
415
416
417
418
419
420
      ->fields($assertion)
      ->execute();
  }

  /**
   * Delete an assertion record by message ID.
   *
   * @param $message_id
   *   Message ID of the assertion to delete.
421
   *
422
423
424
   * @return
   *   TRUE if the assertion was deleted, FALSE otherwise.
   *
425
   * @see \Drupal\simpletest\TestBase::insertAssert()
426
427
   */
  public static function deleteAssert($message_id) {
428
    // We can't use storeAssertion() because this method is static.
429
430
    return (bool) self::getDatabaseConnection()
      ->delete('simpletest')
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
      ->condition('message_id', $message_id)
      ->execute();
  }

  /**
   * Cycles through backtrace until the first non-assertion method is found.
   *
   * @return
   *   Array representing the true caller.
   */
  protected function getAssertionCall() {
    $backtrace = debug_backtrace();

    // The first element is the call. The second element is the caller.
    // We skip calls that occurred in one of the methods of our base classes
    // or in an assertion function.
447
    while (($caller = $backtrace[1]) &&
448
449
450
451
452
453
         ((isset($caller['class']) && isset($this->skipClasses[$caller['class']])) ||
           substr($caller['function'], 0, 6) == 'assert')) {
      // We remove that call.
      array_shift($backtrace);
    }

454
    return Error::getLastCaller($backtrace);
455
456
457
  }

  /**
458
459
460
   * Check to see if a value is not false.
   *
   * False values are: empty string, 0, NULL, and FALSE.
461
462
463
464
   *
   * @param $value
   *   The value on which the assertion is to be done.
   * @param $message
465
   *   (optional) A message to display with the assertion. Do not translate
466
   *   messages: use \Drupal\Component\Render\FormattableMarkup to embed
467
468
   *   variables in the message text, not t(). If left blank, a default message
   *   will be displayed.
469
   * @param $group
470
471
472
473
   *   (optional) The group this message is in, which is displayed in a column
   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
   *   translate this string. Defaults to 'Other'; most tests do not override
   *   this default.
474
   *
475
476
477
478
   * @return
   *   TRUE if the assertion succeeded, FALSE otherwise.
   */
  protected function assertTrue($value, $message = '', $group = 'Other') {
479
    return $this->assert((bool) $value, $message ? $message : new FormattableMarkup('Value @value is TRUE.', ['@value' => var_export($value, TRUE)]), $group);
480
481
482
  }

  /**
483
484
485
   * Check to see if a value is false.
   *
   * False values are: empty string, 0, NULL, and FALSE.
486
487
488
489
   *
   * @param $value
   *   The value on which the assertion is to be done.
   * @param $message
490
   *   (optional) A message to display with the assertion. Do not translate
491
   *   messages: use \Drupal\Component\Render\FormattableMarkup to embed
492
493
   *   variables in the message text, not t(). If left blank, a default message
   *   will be displayed.
494
   * @param $group
495
496
497
498
   *   (optional) The group this message is in, which is displayed in a column
   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
   *   translate this string. Defaults to 'Other'; most tests do not override
   *   this default.
499
   *
500
501
502
503
   * @return
   *   TRUE if the assertion succeeded, FALSE otherwise.
   */
  protected function assertFalse($value, $message = '', $group = 'Other') {
504
    return $this->assert(!$value, $message ? $message : new FormattableMarkup('Value @value is FALSE.', ['@value' => var_export($value, TRUE)]), $group);
505
506
507
508
509
510
511
512
  }

  /**
   * Check to see if a value is NULL.
   *
   * @param $value
   *   The value on which the assertion is to be done.
   * @param $message
513
   *   (optional) A message to display with the assertion. Do not translate
514
   *   messages: use \Drupal\Component\Render\FormattableMarkup to embed
515
516
   *   variables in the message text, not t(). If left blank, a default message
   *   will be displayed.
517
   * @param $group
518
519
520
521
   *   (optional) The group this message is in, which is displayed in a column
   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
   *   translate this string. Defaults to 'Other'; most tests do not override
   *   this default.
522
   *
523
524
525
526
   * @return
   *   TRUE if the assertion succeeded, FALSE otherwise.
   */
  protected function assertNull($value, $message = '', $group = 'Other') {
527
    return $this->assert(!isset($value), $message ? $message : new FormattableMarkup('Value @value is NULL.', ['@value' => var_export($value, TRUE)]), $group);
528
529
530
531
532
533
534
535
  }

  /**
   * Check to see if a value is not NULL.
   *
   * @param $value
   *   The value on which the assertion is to be done.
   * @param $message
536
   *   (optional) A message to display with the assertion. Do not translate
537
   *   messages: use \Drupal\Component\Render\FormattableMarkup to embed
538
539
   *   variables in the message text, not t(). If left blank, a default message
   *   will be displayed.
540
   * @param $group
541
542
543
544
   *   (optional) The group this message is in, which is displayed in a column
   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
   *   translate this string. Defaults to 'Other'; most tests do not override
   *   this default.
545
   *
546
547
548
549
   * @return
   *   TRUE if the assertion succeeded, FALSE otherwise.
   */
  protected function assertNotNull($value, $message = '', $group = 'Other') {
550
    return $this->assert(isset($value), $message ? $message : new FormattableMarkup('Value @value is not NULL.', ['@value' => var_export($value, TRUE)]), $group);
551
552
553
554
555
556
557
558
559
560
  }

  /**
   * Check to see if two values are equal.
   *
   * @param $first
   *   The first value to check.
   * @param $second
   *   The second value to check.
   * @param $message
561
   *   (optional) A message to display with the assertion. Do not translate
562
   *   messages: use \Drupal\Component\Render\FormattableMarkup to embed
563
564
   *   variables in the message text, not t(). If left blank, a default message
   *   will be displayed.
565
   * @param $group
566
567
568
569
   *   (optional) The group this message is in, which is displayed in a column
   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
   *   translate this string. Defaults to 'Other'; most tests do not override
   *   this default.
570
   *
571
572
573
574
   * @return
   *   TRUE if the assertion succeeded, FALSE otherwise.
   */
  protected function assertEqual($first, $second, $message = '', $group = 'Other') {
575
    // Cast objects implementing MarkupInterface to string instead of
576
    // relying on PHP casting them to string depending on what they are being
577
    // comparing with.
578
579
    $first = $this->castSafeStrings($first);
    $second = $this->castSafeStrings($second);
580
581
    $is_equal = $first == $second;
    if (!$is_equal || !$message) {
582
      $default_message = new FormattableMarkup('Value @first is equal to value @second.', ['@first' => var_export($first, TRUE), '@second' => var_export($second, TRUE)]);
583
584
585
      $message = $message ? $message . PHP_EOL . $default_message : $default_message;
    }
    return $this->assert($is_equal, $message, $group);
586
587
588
589
590
591
592
593
594
595
  }

  /**
   * Check to see if two values are not equal.
   *
   * @param $first
   *   The first value to check.
   * @param $second
   *   The second value to check.
   * @param $message
596
   *   (optional) A message to display with the assertion. Do not translate
597
   *   messages: use \Drupal\Component\Render\FormattableMarkup to embed
598
599
   *   variables in the message text, not t(). If left blank, a default message
   *   will be displayed.
600
   * @param $group
601
602
603
604
   *   (optional) The group this message is in, which is displayed in a column
   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
   *   translate this string. Defaults to 'Other'; most tests do not override
   *   this default.
605
   *
606
607
608
609
   * @return
   *   TRUE if the assertion succeeded, FALSE otherwise.
   */
  protected function assertNotEqual($first, $second, $message = '', $group = 'Other') {
610
    // Cast objects implementing MarkupInterface to string instead of
611
612
613
614
615
616
    // relying on PHP casting them to string depending on what they are being
    // comparing with.
    $first = $this->castSafeStrings($first);
    $second = $this->castSafeStrings($second);
    $not_equal = $first != $second;
    if (!$not_equal || !$message) {
617
      $default_message = new FormattableMarkup('Value @first is not equal to value @second.', ['@first' => var_export($first, TRUE), '@second' => var_export($second, TRUE)]);
618
619
620
      $message = $message ? $message . PHP_EOL . $default_message : $default_message;
    }
    return $this->assert($not_equal, $message, $group);
621
622
623
624
625
626
627
628
629
630
  }

  /**
   * Check to see if two values are identical.
   *
   * @param $first
   *   The first value to check.
   * @param $second
   *   The second value to check.
   * @param $message
631
   *   (optional) A message to display with the assertion. Do not translate
632
   *   messages: use \Drupal\Component\Render\FormattableMarkup to embed
633
634
   *   variables in the message text, not t(). If left blank, a default message
   *   will be displayed.
635
   * @param $group
636
637
638
639
   *   (optional) The group this message is in, which is displayed in a column
   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
   *   translate this string. Defaults to 'Other'; most tests do not override
   *   this default.
640
   *
641
642
643
644
   * @return
   *   TRUE if the assertion succeeded, FALSE otherwise.
   */
  protected function assertIdentical($first, $second, $message = '', $group = 'Other') {
645
646
    $is_identical = $first === $second;
    if (!$is_identical || !$message) {
647
      $default_message = new FormattableMarkup('Value @first is identical to value @second.', ['@first' => var_export($first, TRUE), '@second' => var_export($second, TRUE)]);
648
649
650
      $message = $message ? $message . PHP_EOL . $default_message : $default_message;
    }
    return $this->assert($is_identical, $message, $group);
651
652
653
654
655
656
657
658
659
660
  }

  /**
   * Check to see if two values are not identical.
   *
   * @param $first
   *   The first value to check.
   * @param $second
   *   The second value to check.
   * @param $message
661
   *   (optional) A message to display with the assertion. Do not translate
662
   *   messages: use \Drupal\Component\Render\FormattableMarkup to embed
663
664
   *   variables in the message text, not t(). If left blank, a default message
   *   will be displayed.
665
   * @param $group
666
667
668
669
   *   (optional) The group this message is in, which is displayed in a column
   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
   *   translate this string. Defaults to 'Other'; most tests do not override
   *   this default.
670
   *
671
672
673
674
   * @return
   *   TRUE if the assertion succeeded, FALSE otherwise.
   */
  protected function assertNotIdentical($first, $second, $message = '', $group = 'Other') {
675
676
    $not_identical = $first !== $second;
    if (!$not_identical || !$message) {
677
      $default_message = new FormattableMarkup('Value @first is not identical to value @second.', ['@first' => var_export($first, TRUE), '@second' => var_export($second, TRUE)]);
678
679
680
      $message = $message ? $message . PHP_EOL . $default_message : $default_message;
    }
    return $this->assert($not_identical, $message, $group);
681
682
  }

683
684
685
686
687
688
689
690
  /**
   * Checks to see if two objects are identical.
   *
   * @param object $object1
   *   The first object to check.
   * @param object $object2
   *   The second object to check.
   * @param $message
691
   *   (optional) A message to display with the assertion. Do not translate
692
   *   messages: use \Drupal\Component\Render\FormattableMarkup to embed
693
694
   *   variables in the message text, not t(). If left blank, a default message
   *   will be displayed.
695
   * @param $group
696
697
698
699
   *   (optional) The group this message is in, which is displayed in a column
   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
   *   translate this string. Defaults to 'Other'; most tests do not override
   *   this default.
700
701
702
703
   *
   * @return
   *   TRUE if the assertion succeeded, FALSE otherwise.
   */
704
  protected function assertIdenticalObject($object1, $object2, $message = '', $group = 'Other') {
705
    $message = $message ?: new FormattableMarkup('@object1 is identical to @object2', [
706
707
      '@object1' => var_export($object1, TRUE),
      '@object2' => var_export($object2, TRUE),
708
    ]);
709
710
711
712
    $identical = TRUE;
    foreach ($object1 as $key => $value) {
      $identical = $identical && isset($object2->$key) && $object2->$key === $value;
    }
713
    return $this->assertTrue($identical, $message, $group);
714
715
  }

716
717
718
719
720
721
  /**
   * Asserts that no errors have been logged to the PHP error.log thus far.
   *
   * @return bool
   *   TRUE if the assertion succeeded, FALSE otherwise.
   *
722
   * @see \Drupal\simpletest\TestBase::prepareEnvironment()
723
   * @see \Drupal\Core\DrupalKernel::bootConfiguration()
724
725
726
727
728
729
   */
  protected function assertNoErrorsLogged() {
    // Since PHP only creates the error.log file when an actual error is
    // triggered, it is sufficient to check whether the file exists.
    return $this->assertFalse(file_exists(DRUPAL_ROOT . '/' . $this->siteDirectory . '/error.log'), 'PHP error.log is empty.');
  }
730

731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
  /**
   * Asserts that a specific error has been logged to the PHP error log.
   *
   * @param string $error_message
   *   The expected error message.
   *
   * @return bool
   *   TRUE if the assertion succeeded, FALSE otherwise.
   *
   * @see \Drupal\simpletest\TestBase::prepareEnvironment()
   * @see \Drupal\Core\DrupalKernel::bootConfiguration()
   */
  protected function assertErrorLogged($error_message) {
    $error_log_filename = DRUPAL_ROOT . '/' . $this->siteDirectory . '/error.log';
    if (!file_exists($error_log_filename)) {
      $this->error('No error logged yet.');
    }

    $content = file_get_contents($error_log_filename);
    $rows = explode(PHP_EOL, $content);

    // We iterate over the rows in order to be able to remove the logged error
    // afterwards.
    $found = FALSE;
    foreach ($rows as $row_index => $row) {
      if (strpos($content, $error_message) !== FALSE) {
        $found = TRUE;
        unset($rows[$row_index]);
      }
    }

    file_put_contents($error_log_filename, implode("\n", $rows));

    return $this->assertTrue($found, sprintf('The %s error message was logged.', $error_message));
  }

767
768
769
770
  /**
   * Fire an assertion that is always positive.
   *
   * @param $message
771
   *   (optional) A message to display with the assertion. Do not translate
772
   *   messages: use \Drupal\Component\Render\FormattableMarkup to embed
773
774
   *   variables in the message text, not t(). If left blank, a default message
   *   will be displayed.
775
   * @param $group
776
777
778
779
   *   (optional) The group this message is in, which is displayed in a column
   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
   *   translate this string. Defaults to 'Other'; most tests do not override
   *   this default.
780
   *
781
782
783
784
785
786
787
788
789
790
791
   * @return
   *   TRUE.
   */
  protected function pass($message = NULL, $group = 'Other') {
    return $this->assert(TRUE, $message, $group);
  }

  /**
   * Fire an assertion that is always negative.
   *
   * @param $message
792
   *   (optional) A message to display with the assertion. Do not translate
793
   *   messages: use \Drupal\Component\Render\FormattableMarkup to embed
794
795
   *   variables in the message text, not t(). If left blank, a default message
   *   will be displayed.
796
   * @param $group
797
798
799
800
   *   (optional) The group this message is in, which is displayed in a column
   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
   *   translate this string. Defaults to 'Other'; most tests do not override
   *   this default.
801
   *
802
803
804
805
806
807
808
809
810
811
812
   * @return
   *   FALSE.
   */
  protected function fail($message = NULL, $group = 'Other') {
    return $this->assert(FALSE, $message, $group);
  }

  /**
   * Fire an error assertion.
   *
   * @param $message
813
   *   (optional) A message to display with the assertion. Do not translate
814
   *   messages: use \Drupal\Component\Render\FormattableMarkup to embed
815
816
   *   variables in the message text, not t(). If left blank, a default message
   *   will be displayed.
817
   * @param $group
818
819
820
821
   *   (optional) The group this message is in, which is displayed in a column
   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
   *   translate this string. Defaults to 'Other'; most tests do not override
   *   this default.
822
823
   * @param $caller
   *   The caller of the error.
824
   *
825
826
827
828
829
830
831
832
833
834
835
836
837
838
   * @return
   *   FALSE.
   */
  protected function error($message = '', $group = 'Other', array $caller = NULL) {
    if ($group == 'User notice') {
      // Since 'User notice' is set by trigger_error() which is used for debug
      // set the message to a status of 'debug'.
      return $this->assert('debug', $message, 'Debug', $caller);
    }

    return $this->assert('exception', $message, $group, $caller);
  }

  /**
839
   * Logs a verbose message in a text file.
840
   *
841
842
   * The link to the verbose message will be placed in the test results as a
   * passing assertion with the text '[verbose message]'.
843
844
845
846
847
848
849
   *
   * @param $message
   *   The verbose message to be stored.
   *
   * @see simpletest_verbose()
   */
  protected function verbose($message) {
850
851
852
853
854
    // Do nothing if verbose debugging is disabled.
    if (!$this->verbose) {
      return;
    }

855
    $message = '<hr />ID #' . $this->verboseId . ' (<a href="' . $this->verboseClassName . '-' . ($this->verboseId - 1) . '-' . $this->testId . '.html">Previous</a> | <a href="' . $this->verboseClassName . '-' . ($this->verboseId + 1) . '-' . $this->testId . '.html">Next</a>)<hr />' . $message;
856
    $verbose_filename = $this->verboseClassName . '-' . $this->verboseId . '-' . $this->testId . '.html';
857
858
    if (file_put_contents($this->verboseDirectory . '/' . $verbose_filename, $message)) {
      $url = $this->verboseDirectoryUrl . '/' . $verbose_filename;
859
860
      // Not using \Drupal\Core\Utility\LinkGeneratorInterface::generate()
      // to avoid invoking the theme system, so that unit tests
861
      // can use verbose() as well.
862
      $url = '<a href="' . $url . '" target="_blank">Verbose message</a>';
863
      $this->error($url, 'User notice');
864
    }
865
    $this->verboseId++;
866
867
868
869
870
871
872
873
874
875
876
877
878
879
  }

  /**
   * Run all tests in this class.
   *
   * Regardless of whether $methods are passed or not, only method names
   * starting with "test" are executed.
   *
   * @param $methods
   *   (optional) A list of method names in the test case class to run; e.g.,
   *   array('testFoo', 'testBar'). By default, all methods of the class are
   *   taken into account, but it can be useful to only run a few selected test
   *   methods during debugging.
   */
880
  public function run(array $methods = []) {
881
    $this->checkTestHierarchyMismatch();
882
883
884
885
    $class = get_class($this);

    if ($missing_requirements = $this->checkRequirements()) {
      $object_info = new \ReflectionObject($this);
886
      $caller = [
887
        'file' => $object_info->getFileName(),
888
      ];
889
890
891
892
893
894
      foreach ($missing_requirements as $missing_requirement) {
        TestBase::insertAssert($this->testId, $class, FALSE, $missing_requirement, 'Requirements check', $caller);
      }
      return;
    }

895
    TestServiceProvider::$currentTest = $this;
896
    $simpletest_config = $this->config('simpletest.settings');
897

898
899
900
901
    // Unless preset from run-tests.sh, retrieve the current verbose setting.
    if (!isset($this->verbose)) {
      $this->verbose = $simpletest_config->get('verbose');
    }
902

903
    if ($this->verbose) {
904
905
      // Initialize verbose debugging.
      $this->verbose = TRUE;
906
      $this->verboseDirectory = PublicStream::basePath() . '/simpletest/verbose';
907
      $this->verboseDirectoryUrl = file_create_url($this->verboseDirectory);
908
909
910
911
912
      if (file_prepare_directory($this->verboseDirectory, FILE_CREATE_DIRECTORY) && !file_exists($this->verboseDirectory . '/.htaccess')) {
        file_put_contents($this->verboseDirectory . '/.htaccess', "<IfModule mod_expires.c>\nExpiresActive Off\n</IfModule>\n");
      }
      $this->verboseClassName = str_replace("\\", "_", $class);
    }
913
914
    // HTTP auth settings (<username>:<password>) for the simpletest browser
    // when sending requests to the test site.
915
    $this->httpAuthMethod = (int) $simpletest_config->get('httpauth.method');
916
917
918
    $username = $simpletest_config->get('httpauth.username');
    $password = $simpletest_config->get('httpauth.password');
    if (!empty($username) && !empty($password)) {
919
      $this->httpAuthCredentials = $username . ':' . $password;
920
921
    }

922
923
    // Force assertion failures to be thrown as AssertionError for PHP 5 & 7
    // compatibility.
924
    Handle::register();
925

926
    set_error_handler([$this, 'errorHandler']);
927
928
    // Iterate through all the methods in this class, unless a specific list of
    // methods to run was passed.
929
930
931
    $test_methods = array_filter(get_class_methods($class), function ($method) {
      return strpos($method, 'test') === 0;
    });
932
933
934
    if (empty($test_methods)) {
      // Call $this->assert() here because we need to pass along custom caller
      // information, lest the wrong originating code file/line be identified.
935
      $this->assert(FALSE, 'No test methods found.', 'Requirements', ['function' => __METHOD__ . '()', 'file' => __FILE__, 'line' => __LINE__]);
936
    }
937
    if ($methods) {
938
939
940
941
942
943
      $test_methods = array_intersect($test_methods, $methods);
    }
    foreach ($test_methods as $method) {
      // Insert a fail record. This will be deleted on completion to ensure
      // that testing completed.
      $method_info = new \ReflectionMethod($class, $method);
944
      $caller = [
945
946
947
        'file' => $method_info->getFileName(),
        'line' => $method_info->getStartLine(),
        'function' => $class . '->' . $method . '()',
948
      ];
949
950
951
952
      $test_completion_check_id = TestBase::insertAssert($this->testId, $class, FALSE, 'The test did not complete due to a fatal error.', 'Completion check', $caller);

      try {
        $this->prepareEnvironment();
953
      }
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
      catch (\Exception $e) {
        $this->exceptionHandler($e);
        // The prepareEnvironment() method isolates the test from the parent
        // Drupal site by creating a random database prefix and test site
        // directory. If this fails, a test would possibly operate in the
        // parent site. Therefore, the entire test run for this test class
        // has to be aborted.
        // restoreEnvironment() cannot be called, because we do not know
        // where exactly the environment setup failed.
        break;
      }

      try {
        $this->setUp();
      }
      catch (\Exception $e) {
        $this->exceptionHandler($e);
        // Abort if setUp() fails, since all test methods will fail.
        // But ensure to clean up and restore the environment, since
        // prepareEnvironment() succeeded.
        $this->restoreEnvironment();
        break;
976
      }
977
978
      try {
        $this->$method();
979
      }
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
      catch (\Exception $e) {
        $this->exceptionHandler($e);
      }
      try {
        $this->tearDown();
      }
      catch (\Exception $e) {
        $this->exceptionHandler($e);
        // If a test fails to tear down, abort the entire test class, since
        // it is likely that all tests will fail in the same way and a
        // failure here only results in additional test artifacts that have
        // to be manually deleted.
        $this->restoreEnvironment();
        break;
      }

      $this->restoreEnvironment();
      // Remove the test method completion check record.
      TestBase::deleteAssert($test_completion_check_id);
999
    }
1000

1001
    TestServiceProvider::$currentTest = NULL;
1002
    // Clear out the error messages and restore error handler.
1003
    \Drupal::messenger()->deleteAll();
1004
1005
1006
    restore_error_handler();
  }

1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
  /**
   * Generates a database prefix for running tests.
   *
   * The database prefix is used by prepareEnvironment() to setup a public files
   * directory for the test to be run, which also contains the PHP error log,
   * which is written to in case of a fatal error. Since that directory is based
   * on the database prefix, all tests (even unit tests) need to have one, in
   * order to access and read the error log.
   *
   * @see TestBase::prepareEnvironment()
   *
   * The generated database table prefix is used for the Drupal installation
   * being performed for the test. It is also used as user agent HTTP header
1020
1021
1022
1023
   * value by the cURL-based browser of WebTestBase, which is sent to the Drupal
   * installation of the test. During early Drupal bootstrap, the user agent
   * HTTP header is parsed, and if it matches, all database queries use the
   * database table prefix that has been generated here.
1024
1025
1026
1027
   *
   * @see WebTestBase::curlInitialize()
   * @see drupal_valid_test_ua()
   */
1028
  private function prepareDatabasePrefix() {
1029
1030
1031
    $test_db = new TestDatabase();
    $this->siteDirectory = $test_db->getTestSitePath();
    $this->databasePrefix = $test_db->getDatabasePrefix();
1032
1033
1034
1035

    // As soon as the database prefix is set, the test might start to execute.
    // All assertions as well as the SimpleTest batch operations are associated
    // with the testId, so the database prefix has to be associated with it.
1036
    $affected_rows = self::getDatabaseConnection()->update('simpletest_test_id')
1037
      ->fields(['last_prefix' => $this->databasePrefix])
1038
1039
      ->condition('test_id', $this->testId)
      ->execute();
1040
1041
1042
    if (!$affected_rows) {
      throw new \RuntimeException('Failed to set up database prefix.');
    }
1043
1044
  }

1045
1046
1047
  /**
   * Act on global state information before the environment is altered for a test.
   *
1048
   * Allows e.g. KernelTestBase to prime system/extension info from the
1049
1050
1051
1052
   * parent site (and inject it into the test environment so as to improve
   * performance).
   */
  protected function beforePrepareEnvironment() {
1053
1054
1055
1056
1057
1058
1059
  }

  /**
   * Prepares the current environment for running the test.
   *
   * Backups various current environment variables and resets them, so they do
   * not interfere with the Drupal site installation in which tests are executed
1060
   * and can be restored in TestBase::restoreEnvironment().
1061
1062
1063
1064
   *
   * Also sets up new resources for the testing environment, such as the public
   * filesystem and configuration directories.
   *
1065
1066
1067
1068
1069
   * This method is private as it must only be called once by TestBase::run()
   * (multiple invocations for the same test would have unpredictable
   * consequences) and it must not be callable or overridable by test classes.
   *
   * @see TestBase::beforePrepareEnvironment()
1070
   */
1071
  private function prepareEnvironment() {
1072
    $user = \Drupal::currentUser();
1073
1074
1075
1076
1077
1078
    // Allow (base) test classes to backup global state information.
    $this->beforePrepareEnvironment();

    // Create the database prefix for this test.
    $this->prepareDatabasePrefix();

1079
    $language_interface = \Drupal::languageManager()->getCurrentLanguage();
1080

1081
    // When running the test runner within a test, back up the original database
1082
1083
    // prefix.
    if (DRUPAL_TEST_IN_CHILD_SITE) {
1084
1085
1086
      $this->originalPrefix = drupal_valid_test_ua();
    }

1087
    // Backup current in-memory configuration.
1088
1089
    $site_path = \Drupal::service('site.path');
    $this->originalSite = $site_path;
1090
    $this->originalSettings = Settings::getAll();
1091
    $this->originalConfig = $GLOBALS['config'];
1092
    // @todo Remove all remnants of $GLOBALS['conf'].
1093
    // @see https://www.drupal.org/node/2183323
1094
    $this->originalConf = isset($GLOBALS['conf']) ? $GLOBALS['conf'] : NULL;
1095
1096

    // Backup statics and globals.
1097
    $this->originalContainer = \Drupal::getContainer();
1098
    $this->originalLanguage = $language_interface;
1099
    $this->originalConfigDirectories = $GLOBALS['config_directories'];
1100
1101

    // Save further contextual information.
1102
1103
    // Use the original files directory to avoid nesting it within an existing
    // simpletest directory if a test is executed within a test.
1104
    $this->originalFileDirectory = Settings::get('file_public_path', $site_path . '/files');
1105
    $this->originalProfile = drupal_get_profile();
1106
    $this->originalUser = isset($user) ? clone $user : NULL;
1107

1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
    // Prevent that session data is leaked into the UI test runner by closing
    // the session and then setting the session-name (i.e. the name of the
    // session cookie) to a random value. If a test starts a new session, then
    // it will be associated with a different session-name. After the test-run
    // it can be safely destroyed.
    // @see TestBase::restoreEnvironment()
    if (PHP_SAPI !== 'cli' && session_status() === PHP_SESSION_ACTIVE) {
      session_write_close();
    }
    $this->originalSessionName = session_name();
    session_name('SIMPLETEST' . Crypt::randomBytesBase64());
1119
1120
1121
1122
1123
1124
1125

    // Save and clean the shutdown callbacks array because it is static cached
    // and will be changed by the test run. Otherwise it will contain callbacks
    // from both environments and the testing environment will try to call the
    // handlers defined by the original one.
    $callbacks = &drupal_register_shutdown_function();
    $this->originalShutdownCallbacks = $callbacks;
1126
    $callbacks = [];
1127
1128
1129

    // Create test directory ahead of installation so fatal errors and debug
    // information can be logged during installation process.
1130
1131
1132
    file_prepare_directory($this->siteDirectory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);

    // Prepare filesystem directory paths.
1133
1134
1135
1136
    $this->publicFilesDirectory = $this->siteDirectory . '/files';
    $this->privateFilesDirectory = $this->siteDirectory . '/private';
    $this->tempFilesDirectory = $this->siteDirectory . '/temp';
    $this->translationFilesDirectory = $this->siteDirectory . '/translations';
1137

1138
    $this->generatedTestFiles = FALSE;
1139

1140
1141
1142
    // Ensure the configImporter is refreshed for each test.
    $this->configImporter = NULL;

1143
1144
    // Unregister all custom stream wrappers of the parent site.
    // Availability of Drupal stream wrappers varies by test base class:
1145
    // - KernelTestBase supports and maintains stream wrappers in a custom
1146
1147
1148
    //   way.
    // - WebTestBase re-initializes Drupal stream wrappers after installation.
    // The original stream wrappers are restored after the test run.
1149
    // @see TestBase::restoreEnvironment()
1150
    $this->originalContainer->get('stream_wrapper_manager')->unregister();
1151

1152
    // Reset statics.
1153
1154
    drupal_static_reset();

1155
1156
    // Ensure there is no service container.
    $this->container = NULL;
1157
    \Drupal::unsetContainer();
1158

1159
    // Unset globals.
1160
    unset($GLOBALS['config_directories']);
1161
1162
    unset($GLOBALS['config']);
    unset($GLOBALS['conf']);
1163

1164
1165
    // Log fatal errors.
    ini_set('log_errors', 1);
1166
    ini_set('error_log', DRUPAL_ROOT . '/' . $this->siteDirectory . '/error.log');
1167

1168
1169
1170
    // Change the database prefix.
    $this->changeDatabasePrefix();

1171
1172
1173
    // After preparing the environment and changing the database prefix, we are
    // in a valid test environment.
    drupal_valid_test_ua($this->databasePrefix);
1174

1175
    // Reset settings.
1176
    new Settings([
1177
1178
      // For performance, simply use the database prefix as hash salt.
      'hash_salt' => $this->databasePrefix,
1179
      'container_yamls' => [],
1180
    ]);
1181

1182
    drupal_set_time_limit($this->timeLimit);