Further Drupal 8.6.4 changes. Some core files were not committed before a commit...
[yaffs-website] / web / core / modules / system / src / Tests / Entity / EntityCacheTagsTestBase.php
1 <?php
2
3 namespace Drupal\system\Tests\Entity;
4
5 use Drupal\Core\Cache\Cache;
6 use Drupal\Core\Entity\EntityInterface;
7 use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
8 use Drupal\Core\Field\FieldStorageDefinitionInterface;
9 use Drupal\Core\Language\LanguageInterface;
10 use Drupal\Core\Url;
11 use Drupal\field\Entity\FieldStorageConfig;
12 use Drupal\field\Entity\FieldConfig;
13 use Drupal\system\Tests\Cache\PageCacheTagsTestBase;
14 use Drupal\user\Entity\Role;
15 use Drupal\user\RoleInterface;
16
17 @trigger_error(__NAMESPACE__ . '\EntityCacheTagsTestBase is deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0. Instead, use \Drupal\Tests\system\Functional\Entity\EntityCacheTagsTestBase. See https://www.drupal.org/node/2946549.', E_USER_DEPRECATED);
18
19 /**
20  * Provides helper methods for Entity cache tags tests.
21  *
22  * @deprecated Scheduled for removal in Drupal 9.0.0.
23  *   Use \Drupal\Tests\system\Functional\Entity\EntityCacheTagsTestBase instead.
24  *
25  * @see https://www.drupal.org/node/2946549
26  */
27 abstract class EntityCacheTagsTestBase extends PageCacheTagsTestBase {
28
29   /**
30    * Modules to enable.
31    *
32    * @var array
33    */
34   public static $modules = ['entity_test', 'field_test'];
35
36   /**
37    * The main entity used for testing.
38    *
39    * @var \Drupal\Core\Entity\EntityInterface
40    */
41   protected $entity;
42
43   /**
44    * The entity instance referencing the main entity.
45    *
46    * @var \Drupal\Core\Entity\EntityInterface
47    */
48   protected $referencingEntity;
49
50   /**
51    * The entity instance not referencing the main entity.
52    *
53    * @var \Drupal\Core\Entity\EntityInterface
54    */
55   protected $nonReferencingEntity;
56
57   /**
58    * {@inheritdoc}
59    */
60   protected function setUp() {
61     parent::setUp();
62
63     // Give anonymous users permission to view test entities, so that we can
64     // verify the cache tags of cached versions of test entity pages.
65     $user_role = Role::load(RoleInterface::ANONYMOUS_ID);
66     $user_role->grantPermission('view test entity');
67     $user_role->save();
68
69     // Create an entity.
70     $this->entity = $this->createEntity();
71
72     // If this is an entity with field UI enabled, then add a configurable
73     // field. We will use this configurable field in later tests to ensure that
74     // field configuration invalidate render cache entries.
75     if ($this->entity->getEntityType()->get('field_ui_base_route')) {
76       // Add field, so we can modify the field storage and field entities to
77       // verify that changes to those indeed clear cache tags.
78       FieldStorageConfig::create([
79         'field_name' => 'configurable_field',
80         'entity_type' => $this->entity->getEntityTypeId(),
81         'type' => 'test_field',
82         'settings' => [],
83       ])->save();
84       FieldConfig::create([
85         'entity_type' => $this->entity->getEntityTypeId(),
86         'bundle' => $this->entity->bundle(),
87         'field_name' => 'configurable_field',
88         'label' => 'Configurable field',
89         'settings' => [],
90       ])->save();
91
92       // Reload the entity now that a new field has been added to it.
93       $storage = $this->container
94         ->get('entity.manager')
95         ->getStorage($this->entity->getEntityTypeId());
96       $storage->resetCache();
97       $this->entity = $storage->load($this->entity->id());
98     }
99
100     // Create a referencing and a non-referencing entity.
101     list(
102       $this->referencingEntity,
103       $this->nonReferencingEntity,
104     ) = $this->createReferenceTestEntities($this->entity);
105   }
106
107   /**
108    * Generates standardized entity cache tags test info.
109    *
110    * @param string $entity_type_label
111    *   The label of the entity type whose cache tags to test.
112    * @param string $group
113    *   The test group.
114    *
115    * @return array
116    *
117    * @see \Drupal\simpletest\TestBase::getInfo()
118    */
119   protected static function generateStandardizedInfo($entity_type_label, $group) {
120     return [
121       'name' => "$entity_type_label entity cache tags",
122       'description' => "Test the $entity_type_label entity's cache tags.",
123       'group' => $group,
124     ];
125   }
126
127   /**
128    * Creates the entity to be tested.
129    *
130    * @return \Drupal\Core\Entity\EntityInterface
131    *   The entity to be tested.
132    */
133   abstract protected function createEntity();
134
135   /**
136    * Returns the access cache contexts for the tested entity.
137    *
138    * Only list cache contexts that aren't part of the required cache contexts.
139    *
140    * @param \Drupal\Core\Entity\EntityInterface $entity
141    *   The entity to be tested, as created by createEntity().
142    *
143    * @return string[]
144    *   An array of the additional cache contexts.
145    *
146    * @see \Drupal\Core\Entity\EntityAccessControlHandlerInterface
147    */
148   protected function getAccessCacheContextsForEntity(EntityInterface $entity) {
149     return [];
150   }
151
152   /**
153    * Returns the additional (non-standard) cache contexts for the tested entity.
154    *
155    * Only list cache contexts that aren't part of the required cache contexts.
156    *
157    * @param \Drupal\Core\Entity\EntityInterface $entity
158    *   The entity to be tested, as created by createEntity().
159    *
160    * @return string[]
161    *   An array of the additional cache contexts.
162    *
163    * @see \Drupal\system\Tests\Entity\EntityCacheTagsTestBase::createEntity()
164    */
165   protected function getAdditionalCacheContextsForEntity(EntityInterface $entity) {
166     return [];
167   }
168
169   /**
170    * Returns the additional (non-standard) cache tags for the tested entity.
171    *
172    * @param \Drupal\Core\Entity\EntityInterface $entity
173    *   The entity to be tested, as created by createEntity().
174    * @return array
175    *   An array of the additional cache tags.
176    *
177    * @see \Drupal\system\Tests\Entity\EntityCacheTagsTestBase::createEntity()
178    */
179   protected function getAdditionalCacheTagsForEntity(EntityInterface $entity) {
180     return [];
181   }
182
183   /**
184    * Returns the additional cache tags for the tested entity's listing by type.
185    *
186    * @return string[]
187    *   An array of the additional cache contexts.
188    */
189   protected function getAdditionalCacheContextsForEntityListing() {
190     return [];
191   }
192
193   /**
194    * Returns the additional cache tags for the tested entity's listing by type.
195    *
196    * Necessary when there are unavoidable default entities of this type, e.g.
197    * the anonymous and administrator User entities always exist.
198    *
199    * @return array
200    *   An array of the additional cache tags.
201    */
202   protected function getAdditionalCacheTagsForEntityListing() {
203     return [];
204   }
205
206   /**
207    * Selects the preferred view mode for the given entity type.
208    *
209    * Prefers 'full', picks the first one otherwise, and if none are available,
210    * chooses 'default'.
211    */
212   protected function selectViewMode($entity_type) {
213     $view_modes = \Drupal::entityManager()
214       ->getStorage('entity_view_mode')
215       ->loadByProperties(['targetEntityType' => $entity_type]);
216
217     if (empty($view_modes)) {
218       return 'default';
219     }
220     else {
221       // Prefer the "full" display mode.
222       if (isset($view_modes[$entity_type . '.full'])) {
223         return 'full';
224       }
225       else {
226         $view_modes = array_keys($view_modes);
227         return substr($view_modes[0], strlen($entity_type) + 1);
228       }
229     }
230   }
231
232   /**
233    * Creates a referencing and a non-referencing entity for testing purposes.
234    *
235    * @param \Drupal\Core\Entity\EntityInterface $referenced_entity
236    *   The entity that the referencing entity should reference.
237    *
238    * @return \Drupal\Core\Entity\EntityInterface[]
239    *   An array containing a referencing entity and a non-referencing entity.
240    */
241   protected function createReferenceTestEntities($referenced_entity) {
242     // All referencing entities should be of the type 'entity_test'.
243     $entity_type = 'entity_test';
244
245     // Create a "foo" bundle for the given entity type.
246     $bundle = 'foo';
247     entity_test_create_bundle($bundle, NULL, $entity_type);
248
249     // Add a field of the given type to the given entity type's "foo" bundle.
250     $field_name = $referenced_entity->getEntityTypeId() . '_reference';
251     FieldStorageConfig::create([
252       'field_name' => $field_name,
253       'entity_type' => $entity_type,
254       'type' => 'entity_reference',
255       'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
256       'settings' => [
257         'target_type' => $referenced_entity->getEntityTypeId(),
258       ],
259     ])->save();
260     FieldConfig::create([
261       'field_name' => $field_name,
262       'entity_type' => $entity_type,
263       'bundle' => $bundle,
264       'settings' => [
265         'handler' => 'default',
266         'handler_settings' => [
267           'target_bundles' => [
268             $referenced_entity->bundle() => $referenced_entity->bundle(),
269           ],
270           'sort' => ['field' => '_none'],
271           'auto_create' => FALSE,
272         ],
273       ],
274     ])->save();
275     if (!$this->entity->getEntityType()->hasHandlerClass('view_builder')) {
276       entity_get_display($entity_type, $bundle, 'full')
277         ->setComponent($field_name, [
278           'type' => 'entity_reference_label',
279         ])
280         ->save();
281     }
282     else {
283       $referenced_entity_view_mode = $this->selectViewMode($this->entity->getEntityTypeId());
284       entity_get_display($entity_type, $bundle, 'full')
285         ->setComponent($field_name, [
286           'type' => 'entity_reference_entity_view',
287           'settings' => [
288             'view_mode' => $referenced_entity_view_mode,
289           ],
290         ])
291         ->save();
292     }
293
294     // Create an entity that does reference the entity being tested.
295     $label_key = \Drupal::entityManager()->getDefinition($entity_type)->getKey('label');
296     $referencing_entity = $this->container->get('entity_type.manager')
297       ->getStorage($entity_type)
298       ->create([
299         $label_key => 'Referencing ' . $entity_type,
300         'status' => 1,
301         'type' => $bundle,
302         $field_name => ['target_id' => $referenced_entity->id()],
303       ]);
304     $referencing_entity->save();
305
306     // Create an entity that does not reference the entity being tested.
307     $non_referencing_entity = $this->container->get('entity_type.manager')
308       ->getStorage($entity_type)
309       ->create([
310         $label_key => 'Non-referencing ' . $entity_type,
311         'status' => 1,
312         'type' => $bundle,
313       ]);
314     $non_referencing_entity->save();
315
316     return [
317       $referencing_entity,
318       $non_referencing_entity,
319     ];
320   }
321
322   /**
323    * Tests cache tags presence and invalidation of the entity when referenced.
324    *
325    * Tests the following cache tags:
326    * - entity type view cache tag: "<entity type>_view"
327    * - entity cache tag: "<entity type>:<entity ID>"
328    * - entity type list cache tag: "<entity type>_list"
329    * - referencing entity type view cache tag: "<referencing entity type>_view"
330    * - referencing entity type cache tag: "<referencing entity type>:<referencing entity ID>"
331    */
332   public function testReferencedEntity() {
333     $entity_type = $this->entity->getEntityTypeId();
334     $referencing_entity_url = $this->referencingEntity->urlInfo('canonical');
335     $non_referencing_entity_url = $this->nonReferencingEntity->urlInfo('canonical');
336     $listing_url = Url::fromRoute('entity.entity_test.collection_referencing_entities', [
337       'entity_reference_field_name' => $entity_type . '_reference',
338       'referenced_entity_type' => $entity_type,
339       'referenced_entity_id' => $this->entity->id(),
340     ]);
341     $empty_entity_listing_url = Url::fromRoute('entity.entity_test.collection_empty', ['entity_type_id' => $entity_type]);
342     $nonempty_entity_listing_url = Url::fromRoute('entity.entity_test.collection_labels_alphabetically', ['entity_type_id' => $entity_type]);
343
344     // The default cache contexts for rendered entities.
345     $default_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'];
346     $entity_cache_contexts = $default_cache_contexts;
347     $page_cache_contexts = Cache::mergeContexts($default_cache_contexts, ['url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT]);
348
349     // Cache tags present on every rendered page.
350     // 'user.permissions' is a required cache context, and responses that vary
351     // by this cache context when requested by anonymous users automatically
352     // also get this cache tag, to ensure correct invalidation.
353     $page_cache_tags = Cache::mergeTags(['http_response', 'rendered'], ['config:user.role.anonymous']);
354     // If the block module is used, the Block page display variant is used,
355     // which adds the block config entity type's list cache tags.
356     $page_cache_tags = Cache::mergeTags($page_cache_tags, \Drupal::moduleHandler()->moduleExists('block') ? ['config:block_list'] : []);
357
358     $page_cache_tags_referencing_entity = in_array('user.permissions', $this->getAccessCacheContextsForEntity($this->referencingEntity)) ? ['config:user.role.anonymous'] : [];
359
360     $view_cache_tag = [];
361     if ($this->entity->getEntityType()->hasHandlerClass('view_builder')) {
362       $view_cache_tag = \Drupal::entityManager()->getViewBuilder($entity_type)
363         ->getCacheTags();
364     }
365
366     $context_metadata = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($entity_cache_contexts);
367     $cache_context_tags = $context_metadata->getCacheTags();
368
369     // Generate the cache tags for the (non) referencing entities.
370     $referencing_entity_cache_tags = Cache::mergeTags($this->referencingEntity->getCacheTags(), \Drupal::entityManager()->getViewBuilder('entity_test')->getCacheTags());
371     // Includes the main entity's cache tags, since this entity references it.
372     $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $this->entity->getCacheTags());
373     $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $this->getAdditionalCacheTagsForEntity($this->entity));
374     $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $view_cache_tag);
375     $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $cache_context_tags);
376     $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, ['rendered']);
377
378     $non_referencing_entity_cache_tags = Cache::mergeTags($this->nonReferencingEntity->getCacheTags(), \Drupal::entityManager()->getViewBuilder('entity_test')->getCacheTags());
379     $non_referencing_entity_cache_tags = Cache::mergeTags($non_referencing_entity_cache_tags, ['rendered']);
380
381     // Generate the cache tags for all two possible entity listing paths.
382     // 1. list cache tag only (listing query has no match)
383     // 2. list cache tag plus entity cache tag (listing query has a match)
384     $empty_entity_listing_cache_tags = Cache::mergeTags($this->entity->getEntityType()->getListCacheTags(), $page_cache_tags);
385
386     $nonempty_entity_listing_cache_tags = Cache::mergeTags($this->entity->getEntityType()->getListCacheTags(), $this->entity->getCacheTags());
387     $nonempty_entity_listing_cache_tags = Cache::mergeTags($nonempty_entity_listing_cache_tags, $this->getAdditionalCacheTagsForEntityListing($this->entity));
388     $nonempty_entity_listing_cache_tags = Cache::mergeTags($nonempty_entity_listing_cache_tags, $page_cache_tags);
389
390     $this->pass("Test referencing entity.", 'Debug');
391     $this->verifyPageCache($referencing_entity_url, 'MISS');
392
393     // Verify a cache hit, but also the presence of the correct cache tags.
394     $expected_tags = Cache::mergeTags($referencing_entity_cache_tags, $page_cache_tags);
395     $expected_tags = Cache::mergeTags($expected_tags, $page_cache_tags_referencing_entity);
396     $this->verifyPageCache($referencing_entity_url, 'HIT', $expected_tags);
397
398     // Also verify the existence of an entity render cache entry.
399     $cache_keys = ['entity_view', 'entity_test', $this->referencingEntity->id(), 'full'];
400     $cid = $this->createCacheId($cache_keys, $entity_cache_contexts);
401     $access_cache_contexts = $this->getAccessCacheContextsForEntity($this->entity);
402     $additional_cache_contexts = $this->getAdditionalCacheContextsForEntity($this->referencingEntity);
403     $redirected_cid = NULL;
404     if (count($access_cache_contexts) || count($additional_cache_contexts)) {
405       $cache_contexts = Cache::mergeContexts($entity_cache_contexts, $additional_cache_contexts);
406       $cache_contexts = Cache::mergeContexts($cache_contexts, $access_cache_contexts);
407       $redirected_cid = $this->createCacheId($cache_keys, $cache_contexts);
408       $context_metadata = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($cache_contexts);
409       $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $context_metadata->getCacheTags());
410     }
411     $this->verifyRenderCache($cid, $referencing_entity_cache_tags, $redirected_cid);
412
413     $this->pass("Test non-referencing entity.", 'Debug');
414     $this->verifyPageCache($non_referencing_entity_url, 'MISS');
415     // Verify a cache hit, but also the presence of the correct cache tags.
416     $this->verifyPageCache($non_referencing_entity_url, 'HIT', Cache::mergeTags($non_referencing_entity_cache_tags, $page_cache_tags));
417     // Also verify the existence of an entity render cache entry.
418     $cache_keys = ['entity_view', 'entity_test', $this->nonReferencingEntity->id(), 'full'];
419     $cid = $this->createCacheId($cache_keys, $entity_cache_contexts);
420     $this->verifyRenderCache($cid, $non_referencing_entity_cache_tags);
421
422     $this->pass("Test listing of referencing entities.", 'Debug');
423     // Prime the page cache for the listing of referencing entities.
424     $this->verifyPageCache($listing_url, 'MISS');
425
426     // Verify a cache hit, but also the presence of the correct cache tags.
427     $expected_tags = Cache::mergeTags($referencing_entity_cache_tags, $page_cache_tags);
428     $expected_tags = Cache::mergeTags($expected_tags, $page_cache_tags_referencing_entity);
429     $this->verifyPageCache($listing_url, 'HIT', $expected_tags);
430
431     $this->pass("Test empty listing.", 'Debug');
432     // Prime the page cache for the empty listing.
433     $this->verifyPageCache($empty_entity_listing_url, 'MISS');
434     // Verify a cache hit, but also the presence of the correct cache tags.
435     $this->verifyPageCache($empty_entity_listing_url, 'HIT', $empty_entity_listing_cache_tags);
436     // Verify the entity type's list cache contexts are present.
437     $contexts_in_header = $this->drupalGetHeader('X-Drupal-Cache-Contexts');
438     $this->assertEqual(Cache::mergeContexts($page_cache_contexts, $this->getAdditionalCacheContextsForEntityListing()), empty($contexts_in_header) ? [] : explode(' ', $contexts_in_header));
439
440     $this->pass("Test listing containing referenced entity.", 'Debug');
441     // Prime the page cache for the listing containing the referenced entity.
442     $this->verifyPageCache($nonempty_entity_listing_url, 'MISS', $nonempty_entity_listing_cache_tags);
443     // Verify a cache hit, but also the presence of the correct cache tags.
444     $this->verifyPageCache($nonempty_entity_listing_url, 'HIT', $nonempty_entity_listing_cache_tags);
445     // Verify the entity type's list cache contexts are present.
446     $contexts_in_header = $this->drupalGetHeader('X-Drupal-Cache-Contexts');
447     $this->assertEqual(Cache::mergeContexts($page_cache_contexts, $this->getAdditionalCacheContextsForEntityListing()), empty($contexts_in_header) ? [] : explode(' ', $contexts_in_header));
448
449     // Verify that after modifying the referenced entity, there is a cache miss
450     // for every route except the one for the non-referencing entity.
451     $this->pass("Test modification of referenced entity.", 'Debug');
452     $this->entity->save();
453     $this->verifyPageCache($referencing_entity_url, 'MISS');
454     $this->verifyPageCache($listing_url, 'MISS');
455     $this->verifyPageCache($empty_entity_listing_url, 'MISS');
456     $this->verifyPageCache($nonempty_entity_listing_url, 'MISS');
457     $this->verifyPageCache($non_referencing_entity_url, 'HIT');
458
459     // Verify cache hits.
460     $this->verifyPageCache($referencing_entity_url, 'HIT');
461     $this->verifyPageCache($listing_url, 'HIT');
462     $this->verifyPageCache($empty_entity_listing_url, 'HIT');
463     $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
464
465     // Verify that after modifying the referencing entity, there is a cache miss
466     // for every route except the ones for the non-referencing entity and the
467     // empty entity listing.
468     $this->pass("Test modification of referencing entity.", 'Debug');
469     $this->referencingEntity->save();
470     $this->verifyPageCache($referencing_entity_url, 'MISS');
471     $this->verifyPageCache($listing_url, 'MISS');
472     $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
473     $this->verifyPageCache($non_referencing_entity_url, 'HIT');
474     $this->verifyPageCache($empty_entity_listing_url, 'HIT');
475
476     // Verify cache hits.
477     $this->verifyPageCache($referencing_entity_url, 'HIT');
478     $this->verifyPageCache($listing_url, 'HIT');
479     $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
480
481     // Verify that after modifying the non-referencing entity, there is a cache
482     // miss only for the non-referencing entity route.
483     $this->pass("Test modification of non-referencing entity.", 'Debug');
484     $this->nonReferencingEntity->save();
485     $this->verifyPageCache($referencing_entity_url, 'HIT');
486     $this->verifyPageCache($listing_url, 'HIT');
487     $this->verifyPageCache($empty_entity_listing_url, 'HIT');
488     $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
489     $this->verifyPageCache($non_referencing_entity_url, 'MISS');
490
491     // Verify cache hits.
492     $this->verifyPageCache($non_referencing_entity_url, 'HIT');
493
494     if ($this->entity->getEntityType()->hasHandlerClass('view_builder')) {
495       // Verify that after modifying the entity's display, there is a cache miss
496       // for both the referencing entity, and the listing of referencing
497       // entities, but not for any other routes.
498       $referenced_entity_view_mode = $this->selectViewMode($this->entity->getEntityTypeId());
499       $this->pass("Test modification of referenced entity's '$referenced_entity_view_mode' display.", 'Debug');
500       $entity_display = entity_get_display($entity_type, $this->entity->bundle(), $referenced_entity_view_mode);
501       $entity_display->save();
502       $this->verifyPageCache($referencing_entity_url, 'MISS');
503       $this->verifyPageCache($listing_url, 'MISS');
504       $this->verifyPageCache($non_referencing_entity_url, 'HIT');
505       $this->verifyPageCache($empty_entity_listing_url, 'HIT');
506       $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
507
508       // Verify cache hits.
509       $this->verifyPageCache($referencing_entity_url, 'HIT');
510       $this->verifyPageCache($listing_url, 'HIT');
511     }
512
513     if ($bundle_entity_type_id = $this->entity->getEntityType()->getBundleEntityType()) {
514       // Verify that after modifying the corresponding bundle entity, there is a
515       // cache miss for both the referencing entity, and the listing of
516       // referencing entities, but not for any other routes.
517       $this->pass("Test modification of referenced entity's bundle entity.", 'Debug');
518       $bundle_entity = $this->container->get('entity_type.manager')
519         ->getStorage($bundle_entity_type_id)
520         ->load($this->entity->bundle());
521       $bundle_entity->save();
522       $this->verifyPageCache($referencing_entity_url, 'MISS');
523       $this->verifyPageCache($listing_url, 'MISS');
524       $this->verifyPageCache($non_referencing_entity_url, 'HIT');
525       // Special case: entity types may choose to use their bundle entity type
526       // cache tags, to avoid having excessively granular invalidation.
527       $is_special_case = $bundle_entity->getCacheTags() == $this->entity->getCacheTags() && $bundle_entity->getEntityType()->getListCacheTags() == $this->entity->getEntityType()->getListCacheTags();
528       if ($is_special_case) {
529         $this->verifyPageCache($empty_entity_listing_url, 'MISS');
530         $this->verifyPageCache($nonempty_entity_listing_url, 'MISS');
531       }
532       else {
533         $this->verifyPageCache($empty_entity_listing_url, 'HIT');
534         $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
535       }
536
537       // Verify cache hits.
538       $this->verifyPageCache($referencing_entity_url, 'HIT');
539       $this->verifyPageCache($listing_url, 'HIT');
540       if ($is_special_case) {
541         $this->verifyPageCache($empty_entity_listing_url, 'HIT');
542         $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
543       }
544     }
545
546     if ($this->entity->getEntityType()->get('field_ui_base_route')) {
547       // Verify that after modifying a configurable field on the entity, there
548       // is a cache miss.
549       $this->pass("Test modification of referenced entity's configurable field.", 'Debug');
550       $field_storage_name = $this->entity->getEntityTypeId() . '.configurable_field';
551       $field_storage = FieldStorageConfig::load($field_storage_name);
552       $field_storage->save();
553       $this->verifyPageCache($referencing_entity_url, 'MISS');
554       $this->verifyPageCache($listing_url, 'MISS');
555       $this->verifyPageCache($empty_entity_listing_url, 'HIT');
556       $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
557       $this->verifyPageCache($non_referencing_entity_url, 'HIT');
558
559       // Verify cache hits.
560       $this->verifyPageCache($referencing_entity_url, 'HIT');
561       $this->verifyPageCache($listing_url, 'HIT');
562
563       // Verify that after modifying a configurable field on the entity, there
564       // is a cache miss.
565       $this->pass("Test modification of referenced entity's configurable field.", 'Debug');
566       $field_name = $this->entity->getEntityTypeId() . '.' . $this->entity->bundle() . '.configurable_field';
567       $field = FieldConfig::load($field_name);
568       $field->save();
569       $this->verifyPageCache($referencing_entity_url, 'MISS');
570       $this->verifyPageCache($listing_url, 'MISS');
571       $this->verifyPageCache($empty_entity_listing_url, 'HIT');
572       $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
573       $this->verifyPageCache($non_referencing_entity_url, 'HIT');
574
575       // Verify cache hits.
576       $this->verifyPageCache($referencing_entity_url, 'HIT');
577       $this->verifyPageCache($listing_url, 'HIT');
578     }
579
580     // Verify that after invalidating the entity's cache tag directly, there is
581     // a cache miss for every route except the ones for the non-referencing
582     // entity and the empty entity listing.
583     $this->pass("Test invalidation of referenced entity's cache tag.", 'Debug');
584     Cache::invalidateTags($this->entity->getCacheTagsToInvalidate());
585     $this->verifyPageCache($referencing_entity_url, 'MISS');
586     $this->verifyPageCache($listing_url, 'MISS');
587     $this->verifyPageCache($nonempty_entity_listing_url, 'MISS');
588     $this->verifyPageCache($non_referencing_entity_url, 'HIT');
589     $this->verifyPageCache($empty_entity_listing_url, 'HIT');
590
591     // Verify cache hits.
592     $this->verifyPageCache($referencing_entity_url, 'HIT');
593     $this->verifyPageCache($listing_url, 'HIT');
594     $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
595
596     // Verify that after invalidating the entity's list cache tag directly,
597     // there is a cache miss for both the empty entity listing and the non-empty
598     // entity listing routes, but not for other routes.
599     $this->pass("Test invalidation of referenced entity's list cache tag.", 'Debug');
600     Cache::invalidateTags($this->entity->getEntityType()->getListCacheTags());
601     $this->verifyPageCache($empty_entity_listing_url, 'MISS');
602     $this->verifyPageCache($nonempty_entity_listing_url, 'MISS');
603     $this->verifyPageCache($referencing_entity_url, 'HIT');
604     $this->verifyPageCache($non_referencing_entity_url, 'HIT');
605     $this->verifyPageCache($listing_url, 'HIT');
606
607     // Verify cache hits.
608     $this->verifyPageCache($empty_entity_listing_url, 'HIT');
609     $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
610
611     if (!empty($view_cache_tag)) {
612       // Verify that after invalidating the generic entity type's view cache tag
613       // directly, there is a cache miss for both the referencing entity, and the
614       // listing of referencing entities, but not for other routes.
615       $this->pass("Test invalidation of referenced entity's 'view' cache tag.", 'Debug');
616       Cache::invalidateTags($view_cache_tag);
617       $this->verifyPageCache($referencing_entity_url, 'MISS');
618       $this->verifyPageCache($listing_url, 'MISS');
619       $this->verifyPageCache($non_referencing_entity_url, 'HIT');
620       $this->verifyPageCache($empty_entity_listing_url, 'HIT');
621       $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
622
623       // Verify cache hits.
624       $this->verifyPageCache($referencing_entity_url, 'HIT');
625       $this->verifyPageCache($listing_url, 'HIT');
626     }
627
628     // Verify that after deleting the entity, there is a cache miss for every
629     // route except for the non-referencing entity one.
630     $this->pass('Test deletion of referenced entity.', 'Debug');
631     $this->entity->delete();
632     $this->verifyPageCache($referencing_entity_url, 'MISS');
633     $this->verifyPageCache($listing_url, 'MISS');
634     $this->verifyPageCache($empty_entity_listing_url, 'MISS');
635     $this->verifyPageCache($nonempty_entity_listing_url, 'MISS');
636     $this->verifyPageCache($non_referencing_entity_url, 'HIT');
637
638     // Verify cache hits.
639     $referencing_entity_cache_tags = Cache::mergeTags($this->referencingEntity->getCacheTags(), \Drupal::entityManager()->getViewBuilder('entity_test')->getCacheTags());
640     $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, ['http_response', 'rendered']);
641
642     $nonempty_entity_listing_cache_tags = Cache::mergeTags($this->entity->getEntityType()->getListCacheTags(), $this->getAdditionalCacheTagsForEntityListing());
643     $nonempty_entity_listing_cache_tags = Cache::mergeTags($nonempty_entity_listing_cache_tags, $page_cache_tags);
644
645     $this->verifyPageCache($referencing_entity_url, 'HIT', Cache::mergeTags($referencing_entity_cache_tags, $page_cache_tags));
646     $this->verifyPageCache($listing_url, 'HIT', $page_cache_tags);
647     $this->verifyPageCache($empty_entity_listing_url, 'HIT', $empty_entity_listing_cache_tags);
648     $this->verifyPageCache($nonempty_entity_listing_url, 'HIT', $nonempty_entity_listing_cache_tags);
649   }
650
651   /**
652    * Creates a cache ID from a list of cache keys and a set of cache contexts.
653    *
654    * @param string[] $keys
655    *   A list of cache keys.
656    * @param string[] $contexts
657    *   A set of cache contexts.
658    *
659    * @return string
660    *   The cache ID string.
661    */
662   protected function createCacheId(array $keys, array $contexts) {
663     $cid_parts = $keys;
664
665     $contexts = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($contexts);
666     $cid_parts = array_merge($cid_parts, $contexts->getKeys());
667
668     return implode(':', $cid_parts);
669   }
670
671   /**
672    * Verify that a given render cache entry exists, with the correct cache tags.
673    *
674    * @param string $cid
675    *   The render cache item ID.
676    * @param array $tags
677    *   An array of expected cache tags.
678    * @param string|null $redirected_cid
679    *   (optional) The redirected render cache item ID.
680    */
681   protected function verifyRenderCache($cid, array $tags, $redirected_cid = NULL) {
682     // Also verify the existence of an entity render cache entry.
683     $cache_entry = \Drupal::cache('render')->get($cid);
684     $this->assertTrue($cache_entry, 'A render cache entry exists.');
685     sort($cache_entry->tags);
686     sort($tags);
687     $this->assertIdentical($cache_entry->tags, $tags);
688     $is_redirecting_cache_item = isset($cache_entry->data['#cache_redirect']);
689     if ($redirected_cid === NULL) {
690       $this->assertFalse($is_redirecting_cache_item, 'Render cache entry is not a redirect.');
691       // If this is a redirecting cache item unlike we expected, log it.
692       if ($is_redirecting_cache_item) {
693         debug($cache_entry->data);
694       }
695     }
696     else {
697       // Verify that $cid contains a cache redirect.
698       $this->assertTrue($is_redirecting_cache_item, 'Render cache entry is a redirect.');
699       // If this is not a redirecting cache item unlike we expected, log it.
700       if (!$is_redirecting_cache_item) {
701         debug($cache_entry->data);
702       }
703       // Verify that the cache redirect points to the expected CID.
704       $redirect_cache_metadata = $cache_entry->data['#cache'];
705       $actual_redirection_cid = $this->createCacheId(
706         $redirect_cache_metadata['keys'],
707         $redirect_cache_metadata['contexts']
708       );
709       $this->assertIdentical($redirected_cid, $actual_redirection_cid);
710       // Finally, verify that the redirected CID exists and has the same cache
711       // tags.
712       $this->verifyRenderCache($redirected_cid, $tags);
713     }
714   }
715
716 }