diff --git a/core/lib/Drupal/Core/Access/AccessResult.php b/core/lib/Drupal/Core/Access/AccessResult.php index d515933dab895438dedb051fde1295e038c14488..db78d89ee48130fdf6091b784f88ea53ac377ad8 100644 --- a/core/lib/Drupal/Core/Access/AccessResult.php +++ b/core/lib/Drupal/Core/Access/AccessResult.php @@ -466,14 +466,7 @@ public function inheritCacheability(AccessResultInterface $other) { $this->setCacheable($other->isCacheable()); $this->addCacheContexts($other->getCacheContexts()); $this->addCacheTags($other->getCacheTags()); - // Use the lowest max-age. - if ($this->getCacheMaxAge() === Cache::PERMANENT) { - // The other max-age is either lower or equal. - $this->setCacheMaxAge($other->getCacheMaxAge()); - } - else { - $this->setCacheMaxAge(min($this->getCacheMaxAge(), $other->getCacheMaxAge())); - } + $this->setCacheMaxAge(Cache::mergeMaxAges($this->getCacheMaxAge(), $other->getCacheMaxAge())); } // If any of the access results don't provide cacheability metadata, then // we cannot cache the combined access result, for we may not make diff --git a/core/lib/Drupal/Core/Cache/Cache.php b/core/lib/Drupal/Core/Cache/Cache.php index 91cc4a8985b9c020599c75fa7f746ddeccf5d8e3..ff24324f579ad05c645268a542aaeae06fb07e41 100644 --- a/core/lib/Drupal/Core/Cache/Cache.php +++ b/core/lib/Drupal/Core/Cache/Cache.php @@ -70,6 +70,36 @@ public static function mergeTags() { return $cache_tags; } + /** + * Merges max-age values (expressed in seconds), finds the lowest max-age. + * + * Ensures infinite max-age (Cache::PERMANENT) is taken into account. + * + * @param int … + * Max-age values. + * + * @return int + * The minimum max-age value. + */ + public static function mergeMaxAges() { + $max_ages = func_get_args(); + + // Filter out all max-age values set to cache permanently. + if (in_array(Cache::PERMANENT, $max_ages)) { + $max_ages = array_filter($max_ages, function ($max_age) { + return $max_age !== Cache::PERMANENT; + }); + + // If nothing is left, then all max-age values were set to cache + // permanently, and then that is the result. + if (empty($max_ages)) { + return Cache::PERMANENT; + } + } + + return min($max_ages); + } + /** * Validates an array of cache tags. * diff --git a/core/lib/Drupal/Core/Render/BubbleableMetadata.php b/core/lib/Drupal/Core/Render/BubbleableMetadata.php index fa2edf0509a4c70ed583363dc0ebef69be690efa..d822d82bd944d3559bc7e2c8bbe02dafb051c76c 100644 --- a/core/lib/Drupal/Core/Render/BubbleableMetadata.php +++ b/core/lib/Drupal/Core/Render/BubbleableMetadata.php @@ -22,47 +22,35 @@ class BubbleableMetadata { * * @var string[] */ - protected $contexts; + protected $contexts = []; /** * Cache tags. * * @var string[] */ - protected $tags; + protected $tags = []; /** - * Attached assets. + * Cache max-age. * - * @var string[][] + * @var int */ - protected $attached; + protected $maxAge = Cache::PERMANENT; /** - * #post_render_cache metadata. + * Attached assets. * - * @var array[] + * @var string[][] */ - protected $postRenderCache; + protected $attached = []; /** - * Constructs a BubbleableMetadata value object. + * #post_render_cache metadata. * - * @param string[] $contexts - * An array of cache contexts. - * @param string[] $tags - * An array of cache tags. - * @param array $attached - * An array of attached assets. - * @param array $post_render_cache - * An array of #post_render_cache metadata. + * @var array[] */ - public function __construct(array $contexts = [], array $tags = [], array $attached = [], array $post_render_cache = []) { - $this->contexts = $contexts; - $this->tags = $tags; - $this->attached = $attached; - $this->postRenderCache = $post_render_cache; - } + protected $postRenderCache = []; /** * Merges the values of another bubbleable metadata object with this one. @@ -81,6 +69,7 @@ public function merge(BubbleableMetadata $other) { $result = new BubbleableMetadata(); $result->contexts = Cache::mergeContexts($this->contexts, $other->contexts); $result->tags = Cache::mergeTags($this->tags, $other->tags); + $result->maxAge = Cache::mergeMaxAges($this->maxAge, $other->maxAge); $result->attached = Renderer::mergeAttachments($this->attached, $other->attached); $result->postRenderCache = NestedArray::mergeDeep($this->postRenderCache, $other->postRenderCache); return $result; @@ -95,6 +84,7 @@ public function merge(BubbleableMetadata $other) { public function applyTo(array &$build) { $build['#cache']['contexts'] = $this->contexts; $build['#cache']['tags'] = $this->tags; + $build['#cache']['max-age'] = $this->maxAge; $build['#attached'] = $this->attached; $build['#post_render_cache'] = $this->postRenderCache; } @@ -111,9 +101,185 @@ public static function createFromRenderArray(array $build) { $meta = new static(); $meta->contexts = (isset($build['#cache']['contexts'])) ? $build['#cache']['contexts'] : []; $meta->tags = (isset($build['#cache']['tags'])) ? $build['#cache']['tags'] : []; + $meta->maxAge = (isset($build['#cache']['max-age'])) ? $build['#cache']['max-age'] : Cache::PERMANENT; $meta->attached = (isset($build['#attached'])) ? $build['#attached'] : []; $meta->postRenderCache = (isset($build['#post_render_cache'])) ? $build['#post_render_cache'] : []; return $meta; } + /** + * Gets cache tags. + * + * @return string[] + */ + public function getCacheTags() { + return $this->tags; + } + + /** + * Adds cache tags. + * + * @param string[] $cache_tags + * The cache tags to be added. + * + * @return $this + */ + public function addCacheTags(array $cache_tags) { + $this->tags = Cache::mergeTags($this->tags, $cache_tags); + return $this; + } + + /** + * Sets cache tags. + * + * @param string[] $cache_tags + * The cache tags to be associated. + * + * @return $this + */ + public function setCacheTags(array $cache_tags) { + $this->tags = $cache_tags; + return $this; + } + + /** + * Gets cache contexts. + * + * @return string[] + */ + public function getCacheContexts() { + return $this->contexts; + } + + /** + * Adds cache contexts. + * + * @param string[] $cache_contexts + * The cache contexts to be added. + * + * @return $this + */ + public function addCacheContexts(array $cache_contexts) { + $this->contexts = Cache::mergeContexts($this->contexts, $cache_contexts); + return $this; + } + + /** + * Sets cache contexts. + * + * @param string[] $cache_contexts + * The cache contexts to be associated. + * + * @return $this + */ + public function setCacheContexts(array $cache_contexts) { + $this->contexts = $cache_contexts; + return $this; + } + + /** + * Gets the maximum age (in seconds). + * + * @return int + */ + public function getCacheMaxAge() { + return $this->maxAge; + } + + /** + * Sets the maximum age (in seconds). + * + * Defaults to Cache::PERMANENT + * + * @param int $max_age + * The max age to associate. + * + * @return $this + * + * @throws \InvalidArgumentException + */ + public function setCacheMaxAge($max_age) { + if (!is_int($max_age)) { + throw new \InvalidArgumentException('$max_age must be an integer'); + } + + $this->maxAge = $max_age; + return $this; + } + + /** + * Gets assets. + * + * @return array + */ + public function getAssets() { + return $this->attached; + } + + /** + * Adds assets. + * + * @param array $assets + * The associated assets to be attached. + * + * @return $this + */ + public function addAssets(array $assets) { + $this->attached = NestedArray::mergeDeep($this->attached, $assets); + return $this; + } + + /** + * Sets assets. + * + * @param array $assets + * The associated assets to be attached. + * + * @return $this + */ + public function setAssets(array $assets) { + $this->attached = $assets; + return $this; + } + + /** + * Gets #post_render_cache callbacks. + * + * @return array + */ + public function getPostRenderCacheCallbacks() { + return $this->postRenderCache; + } + + /** + * Adds #post_render_cache callbacks. + * + * @param string $callback + * The #post_render_cache callback that will replace the placeholder with + * its eventual markup. + * @param array $context + * An array providing context for the #post_render_cache callback. + * + * @see \Drupal\Core\Render\RendererInterface::generateCachePlaceholder() + * + * @return $this + */ + public function addPostRenderCacheCallback($callback, array $context) { + $this->postRenderCache[$callback][] = $context; + return $this; + } + + /** + * Sets #post_render_cache callbacks. + * + * @param array $post_render_cache_callbacks + * The associated #post_render_cache callbacks to be executed. + * + * @return $this + */ + public function setPostRenderCacheCallbacks(array $post_render_cache_callbacks) { + $this->postRenderCache = $post_render_cache_callbacks; + return $this; + } + } diff --git a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php index 0bcdc5d0311ab7832698df2c96c46fc7449ede02..269b64e314562db602da0463bf7459ffac59fc56 100644 --- a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php +++ b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php @@ -135,21 +135,29 @@ public function renderResponse(array $main_content, Request $request, RouteMatch // entire render cache, regardless of the cache bin. $cache_contexts = []; $cache_tags = ['rendered']; + $cache_max_age = Cache::PERMANENT; foreach (['page_top', 'page', 'page_bottom'] as $region) { if (isset($html[$region])) { $cache_contexts = Cache::mergeContexts($cache_contexts, $html[$region]['#cache']['contexts']); $cache_tags = Cache::mergeTags($cache_tags, $html[$region]['#cache']['tags']); + $cache_max_age = Cache::mergeMaxAges($cache_max_age, $html[$region]['#cache']['max-age']); } } // Set the generator in the HTTP header. list($version) = explode('.', \Drupal::VERSION, 2); - return new Response($content, 200,[ + $response = new Response($content, 200,[ 'X-Drupal-Cache-Tags' => implode(' ', $cache_tags), 'X-Drupal-Cache-Contexts' => implode(' ', $cache_contexts), 'X-Generator' => 'Drupal ' . $version . ' (https://www.drupal.org)' ]); + // If an explicit non-infinite max-age is specified by a part of the page, + // respect that by applying it to the response's headers. + if ($cache_max_age !== Cache::PERMANENT) { + $response->setMaxAge($cache_max_age); + } + return $response; } /** diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php index 6293e8fd7e5477c72420061cf9234f8bfdf465cf..06f9c4f5b655a9d3dfa96b2e00b831f2fc890300 100644 --- a/core/lib/Drupal/Core/Render/Renderer.php +++ b/core/lib/Drupal/Core/Render/Renderer.php @@ -218,6 +218,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) { // Defaults for bubbleable rendering metadata. $elements['#cache']['contexts'] = isset($elements['#cache']['contexts']) ? $elements['#cache']['contexts'] : array(); $elements['#cache']['tags'] = isset($elements['#cache']['tags']) ? $elements['#cache']['tags'] : array(); + $elements['#cache']['max-age'] = isset($elements['#cache']['max-age']) ? $elements['#cache']['max-age'] : Cache::PERMANENT; $elements['#attached'] = isset($elements['#attached']) ? $elements['#attached'] : array(); $elements['#post_render_cache'] = isset($elements['#post_render_cache']) ? $elements['#post_render_cache'] : array(); @@ -723,6 +724,11 @@ protected function cacheSet(array &$elements, $pre_bubbling_cid) { * The cache ID string, or FALSE if the element may not be cached. */ protected function createCacheID(array $elements) { + // If the maximum age is zero, then caching is effectively prohibited. + if (isset($elements['#cache']['max-age']) && $elements['#cache']['max-age'] === 0) { + return FALSE; + } + if (isset($elements['#cache']['cid'])) { return $elements['#cache']['cid']; } diff --git a/core/modules/block/src/BlockViewBuilder.php b/core/modules/block/src/BlockViewBuilder.php index e56b24dc87a22bec29855ff17bfe747a81f1de54..8430393eab69d8b759b41860bbeaa04b941ba22a 100644 --- a/core/modules/block/src/BlockViewBuilder.php +++ b/core/modules/block/src/BlockViewBuilder.php @@ -76,6 +76,8 @@ public function viewMultiple(array $entities = array(), $view_mode = 'full', $la $plugin->getCacheTags() // Block plugin cache tags. ); + $build[$entity_id]['#cache']['max-age'] = $plugin->getCacheMaxAge(); + if ($plugin->isCacheable()) { $build[$entity_id]['#pre_render'][] = array($this, 'buildBlock'); // Generic cache keys, with the block plugin's custom keys appended. diff --git a/core/modules/block/src/Tests/BlockViewBuilderTest.php b/core/modules/block/src/Tests/BlockViewBuilderTest.php index 21eaaca7e0512ec156f2451de3253bb13101496a..e0ee522e98f059f91c663cfbfedeb930004cf372 100644 --- a/core/modules/block/src/Tests/BlockViewBuilderTest.php +++ b/core/modules/block/src/Tests/BlockViewBuilderTest.php @@ -151,7 +151,7 @@ protected function verifyRenderCacheHandling() { // Test that entities with caching disabled do not generate a cache entry. $build = $this->getBlockRenderArray(); - $this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == array('tags'), 'The render array element of uncacheable blocks is not cached, but does have cache tags set.'); + $this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == array('tags', 'max-age'), 'The render array element of uncacheable blocks is not cached, but does have cache tags & max-age set.'); // Enable block caching. $this->setBlockCacheConfig(array( diff --git a/core/modules/filter/src/Element/ProcessedText.php b/core/modules/filter/src/Element/ProcessedText.php index 52f0649ee659e39ab99bba03481c1f1f794830c5..d007b5f86463c9afa9e1d9458fab473c6f6fe433 100644 --- a/core/modules/filter/src/Element/ProcessedText.php +++ b/core/modules/filter/src/Element/ProcessedText.php @@ -113,7 +113,7 @@ public static function preRenderText($element) { foreach ($filters as $filter) { if ($filter_must_be_applied($filter)) { $result = $filter->process($text, $langcode); - $metadata = $metadata->merge($result->getBubbleableMetadata()); + $metadata = $metadata->merge($result); $text = $result->getProcessedText(); } } diff --git a/core/modules/filter/src/FilterProcessResult.php b/core/modules/filter/src/FilterProcessResult.php index e8487d144ea1a1dc58533f8e69ba5ad112821d42..67e49faf51c3c6d93373f382dccde11b0ccd5521 100644 --- a/core/modules/filter/src/FilterProcessResult.php +++ b/core/modules/filter/src/FilterProcessResult.php @@ -21,7 +21,10 @@ * 2. declare cache tags that the filtered text depends upon, so when either of * those cache tags is invalidated, the filtered text should also be * invalidated; - * 3. apply uncacheable filtering, for example because it differs per user. + * 3. declare cache context to vary by, e.g. 'language' to do language-specific + * filtering. + * 4. declare a maximum age for the filtered text + * 5. apply uncacheable filtering, for example because it differs per user. * * In case a filter needs one or more of these advanced use cases, it can use * the additional methods available. @@ -49,14 +52,20 @@ * ), * )); * + * // Associate cache contexts to vary by. + * $result->setCacheContexts(['language']); + * * // Associate cache tags to be invalidated by. * $result->setCacheTags($node->getCacheTags()); * + * // Associate a maximum age. + * $result->setCacheMaxAge(300); // 5 minutes. + * * return $result; * } * @endcode */ -class FilterProcessResult { +class FilterProcessResult extends BubbleableMetadata { /** * The processed text. @@ -67,40 +76,6 @@ class FilterProcessResult { */ protected $processedText; - /** - * An array of associated assets to be attached. - * - * @see drupal_process_attached() - * - * @var array - */ - protected $assets; - - /** - * The attached cache tags. - * - * @see drupal_render_collect_cache_tags() - * - * @var array - */ - protected $cacheTags; - - /** - * The associated cache contexts. - * - * @var string[] - */ - protected $cacheContexts; - - /** - * The associated #post_render_cache callbacks. - * - * @see _drupal_render_process_post_render_cache() - * - * @var array - */ - protected $postRenderCacheCallbacks; - /** * Constructs a FilterProcessResult object. * @@ -109,11 +84,6 @@ class FilterProcessResult { */ public function __construct($processed_text) { $this->processedText = $processed_text; - - $this->assets = array(); - $this->cacheTags = array(); - $this->cacheContexts = array(); - $this->postRenderCacheCallbacks = array(); } /** @@ -146,164 +116,4 @@ public function setProcessedText($processed_text) { $this->processedText = $processed_text; return $this; } - - /** - * Gets cache tags associated with the processed text. - * - * @return array - */ - public function getCacheTags() { - return $this->cacheTags; - } - - /** - * Adds cache tags associated with the processed text. - * - * @param array $cache_tags - * The cache tags to be added. - * - * @return $this - */ - public function addCacheTags(array $cache_tags) { - $this->cacheTags = Cache::mergeTags($this->cacheTags, $cache_tags); - return $this; - } - - /** - * Sets cache tags associated with the processed text. - * - * @param array $cache_tags - * The cache tags to be associated. - * - * @return $this - */ - public function setCacheTags(array $cache_tags) { - $this->cacheTags = $cache_tags; - return $this; - } - - /** - * Gets cache contexts associated with the processed text. - * - * @return string[] - */ - public function getCacheContexts() { - return $this->cacheContexts; - } - - /** - * Adds cache contexts associated with the processed text. - * - * @param string[] $cache_contexts - * The cache contexts to be added. - * - * @return $this - */ - public function addCacheContexts(array $cache_contexts) { - $this->cacheContexts = Cache::mergeContexts($this->cacheContexts, $cache_contexts); - return $this; - } - - /** - * Sets cache contexts associated with the processed text. - * - * @param string[] $cache_contexts - * The cache contexts to be associated. - * - * @return $this - */ - public function setCacheContexts(array $cache_contexts) { - $this->cacheContexts = $cache_contexts; - return $this; - } - - /** - * Gets assets associated with the processed text. - * - * @return array - */ - public function getAssets() { - return $this->assets; - } - - /** - * Adds assets associated with the processed text. - * - * @param array $assets - * The associated assets to be attached. - * - * @return $this - */ - public function addAssets(array $assets) { - $this->assets = NestedArray::mergeDeep($this->assets, $assets); - return $this; - } - - /** - * Sets assets associated with the processed text. - * - * @param array $assets - * The associated assets to be attached. - * - * @return $this - */ - public function setAssets(array $assets) { - $this->assets = $assets; - return $this; - } - - /** - * Gets #post_render_cache callbacks associated with the processed text. - * - * @return array - */ - public function getPostRenderCacheCallbacks() { - return $this->postRenderCacheCallbacks; - } - - /** - * Adds #post_render_cache callbacks associated with the processed text. - * - * @param string $callback - * The #post_render_cache callback that will replace the placeholder with - * its eventual markup. - * @param array $context - * An array providing context for the #post_render_cache callback. - * - * @see drupal_render_cache_generate_placeholder() - * - * @return $this - */ - public function addPostRenderCacheCallback($callback, array $context) { - $this->postRenderCacheCallbacks[$callback][] = $context; - return $this; - } - - /** - * Sets #post_render_cache callbacks associated with the processed text. - * - * @param array $post_render_cache_callbacks - * The associated #post_render_cache callbacks to be executed. - * - * @return $this - */ - public function setPostRenderCacheCallbacks(array $post_render_cache_callbacks) { - $this->postRenderCacheCallbacks = $post_render_cache_callbacks; - return $this; - } - - /** - * Returns the attached asset libraries, etc. as a bubbleable metadata object. - * - * @return \Drupal\Core\Render\BubbleableMetadata - */ - public function getBubbleableMetadata() { - return new BubbleableMetadata( - $this->getCacheContexts(), - $this->getCacheTags(), - $this->getAssets(), - $this->getPostRenderCacheCallbacks() - ); - } - } diff --git a/core/tests/Drupal/Tests/Core/Cache/CacheTest.php b/core/tests/Drupal/Tests/Core/Cache/CacheTest.php index 75362a7a95170159bf12e730baac8b987e8bf3d3..261aa809c12829831f9ddc0158a8fb71f347124a 100644 --- a/core/tests/Drupal/Tests/Core/Cache/CacheTest.php +++ b/core/tests/Drupal/Tests/Core/Cache/CacheTest.php @@ -83,6 +83,38 @@ public function testMergeTags(array $a, array $b, array $expected) { $this->assertEquals($expected, Cache::mergeTags($a, $b)); } + /** + * Provides a list of pairs of cache tags arrays to be merged. + * + * @return array + */ + public function mergeMaxAgesProvider() { + return [ + [Cache::PERMANENT, Cache::PERMANENT, Cache::PERMANENT], + [60, 60, 60], + [0, 0, 0], + + [60, 0, 0], + [0, 60, 0], + + [Cache::PERMANENT, 0, 0], + [0, Cache::PERMANENT, 0], + + [Cache::PERMANENT, 60, 60], + [60, Cache::PERMANENT, 60], + ]; + } + + + /** + * @covers ::mergeMaxAges + * + * @dataProvider mergeMaxAgesProvider + */ + public function testMergeMaxAges($a, $b, $expected) { + $this->assertSame($expected, Cache::mergeMaxAges($a, $b)); + } + /** * Provides a list of pairs of (prefix, suffixes) to build cache tags from. * diff --git a/core/tests/Drupal/Tests/Core/Render/BubbleableMetadataTest.php b/core/tests/Drupal/Tests/Core/Render/BubbleableMetadataTest.php index ea336dada4bba08785fc9ee8ea55b291bfbd5c2c..f6c4cfb5db70714528b0c33efeb8783092d398ff 100644 --- a/core/tests/Drupal/Tests/Core/Render/BubbleableMetadataTest.php +++ b/core/tests/Drupal/Tests/Core/Render/BubbleableMetadataTest.php @@ -7,6 +7,7 @@ namespace Drupal\Tests\Core\Render; +use Drupal\Core\Cache\Cache; use Drupal\Core\Render\BubbleableMetadata; use Drupal\Tests\UnitTestCase; use Drupal\Core\Render\Element; @@ -35,13 +36,17 @@ public function providerTestApplyTo() { $data = []; $empty_metadata = new BubbleableMetadata(); - $nonempty_metadata = new BubbleableMetadata(['qux'], ['foo:bar'], ['settings' => ['foo' => 'bar']]); + $nonempty_metadata = new BubbleableMetadata(); + $nonempty_metadata->setCacheContexts(['qux']) + ->setCacheTags(['foo:bar']) + ->setAssets(['settings' => ['foo' => 'bar']]); $empty_render_array = []; $nonempty_render_array = [ '#cache' => [ 'contexts' => ['qux'], 'tags' => ['llamas:are:awesome:but:kittens:too'], + 'max-age' => Cache::PERMANENT, ], '#attached' => [ 'library' => [ @@ -56,6 +61,7 @@ public function providerTestApplyTo() { '#cache' => [ 'contexts' => [], 'tags' => [], + 'max-age' => Cache::PERMANENT, ], '#attached' => [], '#post_render_cache' => [], @@ -66,6 +72,7 @@ public function providerTestApplyTo() { '#cache' => [ 'contexts' => ['qux'], 'tags' => ['foo:bar'], + 'max-age' => Cache::PERMANENT, ], '#attached' => [ 'settings' => [ @@ -97,13 +104,17 @@ public function providerTestCreateFromRenderArray() { $data = []; $empty_metadata = new BubbleableMetadata(); - $nonempty_metadata = new BubbleableMetadata(['qux'], ['foo:bar'], ['settings' => ['foo' => 'bar']]); + $nonempty_metadata = new BubbleableMetadata(); + $nonempty_metadata->setCacheContexts(['qux']) + ->setCacheTags(['foo:bar']) + ->setAssets(['settings' => ['foo' => 'bar']]); $empty_render_array = []; $nonempty_render_array = [ '#cache' => [ 'contexts' => ['qux'], 'tags' => ['foo:bar'], + 'max-age' => Cache::PERMANENT, ], '#attached' => [ 'settings' => [ diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTest.php b/core/tests/Drupal/Tests/Core/Render/RendererTest.php index ac2925c655c1a5c1eacc5ec6ec462c1fe227e528..a53155f6dae3c6bbe29b32f5c7814aa01c541c52 100644 --- a/core/tests/Drupal/Tests/Core/Render/RendererTest.php +++ b/core/tests/Drupal/Tests/Core/Render/RendererTest.php @@ -21,6 +21,7 @@ class RendererTest extends RendererTestBase { '#cache' => [ 'contexts' => [], 'tags' => [], + 'max-age' => Cache::PERMANENT, ], '#attached' => [], '#post_render_cache' => [],