diff --git a/core/lib/Drupal/Core/EventSubscriber/ExceptionTestSiteSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ExceptionTestSiteSubscriber.php index d8f1a61..8cbf03c 100644 --- a/core/lib/Drupal/Core/EventSubscriber/ExceptionTestSiteSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/ExceptionTestSiteSubscriber.php @@ -8,7 +8,9 @@ namespace Drupal\Core\EventSubscriber; use Drupal\Core\Utility\Error; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\KernelEvents; /** * Custom handling of errors when in a system-under-test. @@ -46,7 +48,7 @@ public function on500(GetResponseForExceptionEvent $event) { // When running inside the testing framework, we relay the errors // to the tested site by the way of HTTP headers. - if (DRUPAL_TEST_IN_CHILD_SITE && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) { + if ($this->canAddHeaders()) { // $number does not use drupal_static as it should not be reset // as it uniquely identifies each PHP error. static $number = 0; @@ -64,4 +66,38 @@ public function on500(GetResponseForExceptionEvent $event) { } } + /** + * Handles response being sent. + * + * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event + * The event to process. + */ + public function onResponse(FilterResponseEvent $event) { + if ($this->canAddHeaders()) { + $event->getResponse()->headers->add([ + 'X-Drupal-Memory-Peak' => memory_get_peak_usage(TRUE), + ]); + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events = parent::getSubscribedEvents(); + // Add a listener for outputting peak memory usage. + $events[KernelEvents::RESPONSE][] = ['onResponse', 100]; + return $events; + } + + /** + * Indicates whether headers should be added. + * + * @return bool + * TRUE if headers should be added. + */ + protected function canAddHeaders() { + return (DRUPAL_TEST_IN_CHILD_SITE && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)); + } + } diff --git a/core/modules/simpletest/src/Tests/BrokenSetUpTest.php b/core/modules/simpletest/src/Tests/BrokenSetUpTest.php index e5c1ebb..74ac44e 100644 --- a/core/modules/simpletest/src/Tests/BrokenSetUpTest.php +++ b/core/modules/simpletest/src/Tests/BrokenSetUpTest.php @@ -30,6 +30,11 @@ class BrokenSetUpTest extends WebTestBase { public static $modules = array('simpletest'); /** + * Set no memory limit for this test. The SimpleTest UI is very expensive. + */ + protected $memoryUsageTrigger = FALSE; + + /** * The path to the shared trigger file. * * @var string diff --git a/core/modules/simpletest/src/Tests/InstallationProfileModuleTestsTest.php b/core/modules/simpletest/src/Tests/InstallationProfileModuleTestsTest.php index 544dabd..eac8f74 100644 --- a/core/modules/simpletest/src/Tests/InstallationProfileModuleTestsTest.php +++ b/core/modules/simpletest/src/Tests/InstallationProfileModuleTestsTest.php @@ -31,6 +31,11 @@ class InstallationProfileModuleTestsTest extends WebTestBase { protected $adminUser; /** + * Set no memory limit for this test. The SimpleTest UI is very expensive. + */ + protected $memoryUsageTrigger = FALSE; + + /** * Use the Testing profile. * * The Testing profile contains drupal_system_listing_compatible_test.test, diff --git a/core/modules/simpletest/src/Tests/MissingCheckedRequirementsTest.php b/core/modules/simpletest/src/Tests/MissingCheckedRequirementsTest.php index fd8d8e0..81c4b3c 100644 --- a/core/modules/simpletest/src/Tests/MissingCheckedRequirementsTest.php +++ b/core/modules/simpletest/src/Tests/MissingCheckedRequirementsTest.php @@ -23,6 +23,11 @@ class MissingCheckedRequirementsTest extends WebTestBase { */ public static $modules = array('simpletest'); + /** + * Set no memory limit for this test. The SimpleTest UI is very expensive. + */ + protected $memoryUsageTrigger = FALSE; + protected function setUp() { parent::setUp(); $admin_user = $this->drupalCreateUser(array('administer unit tests')); diff --git a/core/modules/simpletest/src/Tests/OtherInstallationProfileTestsTest.php b/core/modules/simpletest/src/Tests/OtherInstallationProfileTestsTest.php index 9cfbfff..9d03383 100644 --- a/core/modules/simpletest/src/Tests/OtherInstallationProfileTestsTest.php +++ b/core/modules/simpletest/src/Tests/OtherInstallationProfileTestsTest.php @@ -39,6 +39,11 @@ class OtherInstallationProfileTestsTest extends WebTestBase { protected $profile = 'minimal'; /** + * Set no memory limit for this test. The SimpleTest UI is very expensive. + */ + protected $memoryUsageTrigger = FALSE; + + /** * An administrative user with permission to administer unit tests. * * @var \Drupal\user\UserInterface diff --git a/core/modules/simpletest/src/Tests/SimpleTestBrowserTest.php b/core/modules/simpletest/src/Tests/SimpleTestBrowserTest.php index 574da77..c76e315 100644 --- a/core/modules/simpletest/src/Tests/SimpleTestBrowserTest.php +++ b/core/modules/simpletest/src/Tests/SimpleTestBrowserTest.php @@ -24,6 +24,11 @@ class SimpleTestBrowserTest extends WebTestBase { */ public static $modules = array('simpletest', 'test_page_test'); + /** + * Set no memory limit for this test. The SimpleTest UI is very expensive. + */ + protected $memoryUsageTrigger = FALSE; + public function setUp() { parent::setUp(); // Create and log in an admin user. diff --git a/core/modules/simpletest/src/Tests/SimpleTestTest.php b/core/modules/simpletest/src/Tests/SimpleTestTest.php index 1f97845..2dcb63e 100644 --- a/core/modules/simpletest/src/Tests/SimpleTestTest.php +++ b/core/modules/simpletest/src/Tests/SimpleTestTest.php @@ -8,7 +8,9 @@ namespace Drupal\simpletest\Tests; use Drupal\Component\Utility\Crypt; +use Drupal\Core\Url; use Drupal\simpletest\WebTestBase; +use Drupal\user\Entity\User; /** * Tests SimpleTest's web interface: check that the intended tests were run and @@ -27,6 +29,11 @@ class SimpleTestTest extends WebTestBase { public static $modules = ['simpletest']; /** + * Set no memory limit for this test. The SimpleTest UI is very expensive. + */ + protected $memoryUsageTrigger = FALSE; + + /** * The results array that has been parsed by getTestResults(). * * @var array @@ -212,6 +219,14 @@ function stubTest() { // This causes the debug message asserted in confirmStubResults(). debug('Foo', 'Debug', FALSE); + + // This causes \Drupal\simpletest\WebTestBase::curlHeaderCallback() to fail. + // Set an absurdly low limit and get a page. + $old_limit = $this->memoryUsageTrigger; + $this->memoryUsageTrigger = '1M'; + // Getting the front page will result in a redirect to /user/2. + $this->drupalGet('/'); + $this->memoryUsageTrigger = $old_limit; } /** @@ -250,7 +265,12 @@ function confirmStubTestResults() { $this->assertAssertion("Debug: 'Foo'", 'Debug', 'Fail', 'SimpleTestTest.php', 'Drupal\simpletest\Tests\SimpleTestTest->stubTest()'); - $this->assertEqual('15 passes, 3 fails, 2 exceptions, 3 debug messages', $this->childTestResults['summary']); + // Check that we have two exceeded memory errors. One for the frontpage and + // one for the user page which the request is redirected to. + $this->assertAssertion('exceeded acceptable maximum (1M) on url ' . Url::fromRoute('')->setAbsolute()->toString(), 'Other', 'Fail', 'WebTestBase.php', 'Drupal\simpletest\WebTestBase->curlExec()'); + $this->assertAssertion('exceeded acceptable maximum (1M) on url ' . User::load(2)->urlInfo()->setAbsolute()->toString(), 'Other', 'Fail', 'WebTestBase.php', 'Drupal\simpletest\WebTestBase->curlExec()'); + + $this->assertEqual('17 passes, 5 fails, 2 exceptions, 4 debug messages', $this->childTestResults['summary']); $this->testIds[] = $test_id = $this->getTestIdFromResults(); $this->assertTrue($test_id, 'Found test ID in results.'); diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php index 2ac0d4b..7dd405b 100644 --- a/core/modules/simpletest/src/WebTestBase.php +++ b/core/modules/simpletest/src/WebTestBase.php @@ -11,6 +11,7 @@ use Drupal\Component\FileCache\FileCacheFactory; use Drupal\Component\Serialization\Json; use Drupal\Component\Serialization\Yaml; +use Drupal\Component\Utility\Bytes; use Drupal\Component\Utility\Html; use Drupal\Component\Utility\NestedArray; use Drupal\Component\Utility\UrlHelper; @@ -64,6 +65,20 @@ protected $url; /** + * The maximum memory usage to allow on the child site, in MB. + * + * The minimum amount of memory Drupal expects to have available is defined by + * DRUPAL_MINIMUM_PHP_MEMORY_LIMIT. This default value on the base class + * should be less than or equal to DRUPAL_MINIMUM_PHP_MEMORY_LIMIT. If a + * module requires more memory than this limit, the module should add a + * check to its hook_requirements() and use the higher value in its tests. + * The check can be disabled for a specific test by setting it to FALSE. + * + * @var string|FALSE + */ + protected $memoryUsageTrigger = '48M'; + + /** * The handle of the current cURL connection. * * @var resource @@ -1422,6 +1437,13 @@ protected function curlHeaderCallback($curlHandler, $header) { // the header. call_user_func_array(array(&$this, 'error'), unserialize(urldecode($matches[1]))); } + if ($this->memoryUsageTrigger && preg_match('/^X-Drupal-Memory-Peak: (.*)$/', $header, $matches) && (float) $matches[1] >= Bytes::toInt($this->memoryUsageTrigger)) { + $this->fail(SafeMarkup::format('Child site memory usage (@site_usage) exceeded acceptable maximum (@max_usage) on url @url', [ + '@url' => curl_getinfo($curlHandler, CURLINFO_EFFECTIVE_URL), + '@site_usage' => format_size($matches[1]), + '@max_usage' => $this->memoryUsageTrigger, + ])); + } // Save cookies. if (preg_match('/^Set-Cookie: ([^=]+)=(.+)/', $header, $matches)) {