 core/lib/Drupal/Core/Entity/EntityViewBuilder.php  |   12 +-
 .../Tests/CustomBlockCacheTagsTest.php             |   44 +++
 .../Drupal/comment/Tests/CommentCacheTagsTest.php  |   74 +++++
 .../lib/Drupal/menu/Tests/MenuCacheTagsTest.php    |  114 +++++++
 .../menu/lib/Drupal/menu/Tests/MenuTest.php        |   60 ----
 .../node/lib/Drupal/node/Form/DeleteMultiple.php   |    2 -
 .../node/lib/Drupal/node/Form/NodeDeleteForm.php   |    2 -
 .../lib/Drupal/node/Tests/NodeCacheTagsTest.php    |   56 ++++
 .../lib/Drupal/node/Tests/NodePageCacheTest.php    |   97 ------
 .../system/Tests/Cache/PageCacheTagsTestBase.php   |   56 ++++
 .../Tests/Entity/EntityCacheTagsTestBase.php       |  339 ++++++++++++++++++++
 .../system/Tests/Entity/EntityViewBuilderTest.php  |    8 +-
 .../Entity/EntityWithUriCacheTagsTestBase.php      |   96 ++++++
 .../modules/entity_test/entity_test.routing.yml    |    8 +
 .../Controller/EntityTestController.php            |   28 ++
 .../Drupal/taxonomy/Tests/TermCacheTagsTest.php    |   50 +++
 .../lib/Drupal/user/Tests/UserCacheTagsTest.php    |   55 ++++
 17 files changed, 931 insertions(+), 170 deletions(-)

diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
index 751f021..8ccf60a 100644
--- a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
+++ b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
@@ -143,19 +143,21 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langco
       "#{$this->entityTypeId}" => $entity,
       '#view_mode' => $view_mode,
       '#langcode' => $langcode,
+      '#cache' => array(
+        'tags' =>  array(
+          $this->entityTypeId . '_view' => TRUE,
+          $this->entityTypeId => array($entity->id()),
+        ),
+      )
     );
 
     // Cache the rendered output if permitted by the view mode and global entity
     // type configuration.
     if ($this->isViewModeCacheable($view_mode) && !$entity->isNew() && !isset($entity->in_preview) && $this->entityType->isRenderCacheable()) {
-      $return['#cache'] = array(
+      $return['#cache'] += array(
         'keys' => array('entity_view', $this->entityTypeId, $entity->id(), $view_mode),
         'granularity' => DRUPAL_CACHE_PER_ROLE,
         'bin' => $this->cacheBin,
-        'tags' => array(
-          $this->entityTypeId . '_view' => TRUE,
-          $this->entityTypeId => array($entity->id()),
-        ),
       );
     }
 
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockCacheTagsTest.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockCacheTagsTest.php
new file mode 100644
index 0000000..9d2139a
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockCacheTagsTest.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\custom_block\Tests\CustomBlockCacheTagsTest.
+ */
+
+namespace Drupal\custom_block\Tests;
+
+use Drupal\system\Tests\Entity\EntityCacheTagsTestBase;
+
+/**
+ * Tests the Custom Block entity's cache tags.
+ */
+class CustomBlockCacheTagsTest extends EntityCacheTagsTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = array('custom_block');
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return static::generateStandardizedInfo('custom_block', 'Custom Block');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static function createEntity() {
+    // Create a "Llama" custom block.
+    $custom_block = entity_create('custom_block', array(
+      'info' => 'Llama',
+      'type' => 'basic',
+      'body' => 'The name "llama" was adopted by European settlers from native Peruvians.',
+    ));
+    $custom_block->save();
+
+    return $custom_block;
+  }
+
+}
diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentCacheTagsTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentCacheTagsTest.php
new file mode 100644
index 0000000..4af30b1
--- /dev/null
+++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentCacheTagsTest.php
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\comment\Tests\CommentCacheTagsTest.
+ */
+
+namespace Drupal\comment\Tests;
+
+use Drupal\system\Tests\Entity\EntityWithUriCacheTagsTestBase;
+
+/**
+ * Tests the Comment entity's cache tags.
+ */
+class CommentCacheTagsTest extends EntityWithUriCacheTagsTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = array('comment');
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return static::generateStandardizedInfo('comment', 'Comment');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    // Give anonymous users permission to view comments, so that we can verify
+    // the cache tags of cached versions of comment pages.
+    $user_role = entity_load('user_role', DRUPAL_ANONYMOUS_RID);
+    $user_role->grantPermission('access comments');
+    $user_role->save();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static function createEntity() {
+    // Create a "bar" bundle for the "entity_test" entity type and create.
+    $bundle = 'bar';
+    entity_test_create_bundle($bundle, NULL, 'entity_test');
+
+    // Create a comment field on this bundle.
+    \Drupal::service('comment.manager')->addDefaultField('entity_test', 'bar');
+
+    // Create a "Camelids" test entity.
+    $entity_test = entity_create('entity_test', array(
+      'name' => 'Camelids',
+      'type' => 'bar',
+    ));
+    $entity_test->save();
+
+    // Create a "Llama" taxonomy term.
+    $comment = entity_create('comment', array(
+      'subject' => 'Llama',
+      'comment_body' => 'The name "llama" was adopted by European settlers from native Peruvians.',
+      'entity_id' => $entity_test->id(),
+      'entity_type' => 'entity_test',
+      'field_name' => 'comment',
+      'status' => \Drupal\comment\CommentInterface::PUBLISHED,
+    ));
+    $comment->save();
+
+    return $comment;
+  }
+
+}
diff --git a/core/modules/menu/lib/Drupal/menu/Tests/MenuCacheTagsTest.php b/core/modules/menu/lib/Drupal/menu/Tests/MenuCacheTagsTest.php
new file mode 100644
index 0000000..14c6168
--- /dev/null
+++ b/core/modules/menu/lib/Drupal/menu/Tests/MenuCacheTagsTest.php
@@ -0,0 +1,114 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu\Tests\MenuCacheTagsTest.
+ */
+
+namespace Drupal\menu\Tests;
+
+use Drupal\system\Tests\Cache\PageCacheTagsTestBase;
+
+/**
+ * Tests the Menu and Menu Link entities' cache tags.
+ */
+class MenuCacheTagsTest extends PageCacheTagsTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = array('menu', 'block', 'test_page_test');
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => "Menu & Menu link entities cache tags",
+      'description' => "Test the Menu & Menu link entities' cache tags.",
+      'group' => 'Menu',
+    );
+  }
+
+  /**
+   * Tests cache tags presence and invalidation of the Menu entity.
+   *
+   * Tests the following cache tags:
+   * - "menu:<menu ID>"
+   */
+  public function testMenuBlock() {
+    // Create a Llama menu, add a link to it and place the corresponding block.
+    $menu = entity_create('menu', array(
+      'id' => 'llama',
+      'label' => 'Llama',
+      'description' => 'Description text',
+    ));
+    $menu->save();
+    $menu_link = entity_create('menu_link', array(
+      'link_path' => '<front>',
+      'link_title' => 'Vicuña',
+      'menu_name' => 'llama',
+    ));
+    $menu_link->save();
+    $block = $this->drupalPlaceBlock('system_menu_block:llama', array('label' => 'Llama', 'module' => 'system', 'region' => 'footer'));
+
+    // Prime the page cache.
+    $this->verifyPageCache('test-page', 'MISS');
+
+    // Verify a cache hit, but also the presence of the correct cache tags.
+    $this->verifyPageCache('test-page', 'HIT', array('content:1', 'menu:llama'));
+
+
+    // Verify that after modifying the menu, there is a cache miss.
+    $this->pass('Test modification of menu.', 'Debug');
+    $menu->label = 'Awesome llama';
+    $menu->save();
+    $this->verifyPageCache('test-page', 'MISS');
+
+    // Verify a cache hit.
+    $this->verifyPageCache('test-page', 'HIT');
+
+
+    // Verify that after modifying the menu link, there is a cache miss.
+    $this->pass('Test modification of menu link.', 'Debug');
+    $menu_link->link_title = 'Guanaco';
+    $menu_link->save();
+    $this->verifyPageCache('test-page', 'MISS');
+
+    // Verify a cache hit.
+    $this->verifyPageCache('test-page', 'HIT');
+
+
+    // Verify that after adding a menu link, there is a cache miss.
+    $this->pass('Test addition of menu link.', 'Debug');
+    $menu_link_2 = entity_create('menu_link', array(
+      'link_path' => '<front>',
+      'link_title' => 'Alpaca',
+      'menu_name' => 'llama',
+    ));
+    $menu_link_2->save();
+    $this->verifyPageCache('test-page', 'MISS');
+
+    // Verify a cache hit.
+    $this->verifyPageCache('test-page', 'HIT');
+
+
+    // Verify that after deleting the first menu link, there is a cache miss.
+    $this->pass('Test deletion of menu link.', 'Debug');
+    $menu_link->delete();
+    $this->verifyPageCache('test-page', 'MISS');
+
+    // Verify a cache hit.
+    $this->verifyPageCache('test-page', 'HIT', array('content:1', 'menu:llama'));
+
+
+    // Verify that after deleting the menu, there is a cache miss.
+    $this->pass('Test deletion of menu.', 'Debug');
+    $menu->delete();
+    $this->verifyPageCache('test-page', 'MISS');
+
+    // Verify a cache hit.
+    $this->verifyPageCache('test-page', 'HIT', array('content:1'));
+  }
+
+}
diff --git a/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php b/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php
index d926528..ef63ae0 100644
--- a/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php
+++ b/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php
@@ -479,66 +479,6 @@ public function testBlockContextualLinks() {
   }
 
   /**
-   * Test that cache tags are properly set and bubbled up to the page cache.
-   *
-   * Ensures that invalidation of the "menu:<menu name>" cache tags works.
-   */
-  public function testMenuBlockPageCacheTags() {
-    // Enable page caching.
-    $config = \Drupal::config('system.performance');
-    $config->set('cache.page.use_internal', 1);
-    $config->set('cache.page.max_age', 300);
-    $config->save();
-
-    // Create a Llama menu, add a link to it and place the corresponding block.
-    $menu = entity_create('menu', array(
-      'id' => 'llama',
-      'label' => 'Llama',
-      'description' => 'Description text',
-    ));
-    $menu->save();
-    $menu_link = entity_create('menu_link', array(
-      'link_path' => '<front>',
-      'link_title' => 'Vicuña',
-      'menu_name' => 'llama',
-    ));
-    $menu_link->save();
-    $block = $this->drupalPlaceBlock('system_menu_block:llama', array('label' => 'Llama', 'module' => 'system', 'region' => 'footer'));
-
-    // Prime the page cache.
-    $this->drupalGet('test-page');
-    $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
-
-    // Verify a cache hit, but also the presence of the correct cache tags.
-    $this->drupalGet('test-page');
-    $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT');
-    $cid_parts = array(url('test-page', array('absolute' => TRUE)), 'html');
-    $cid = sha1(implode(':', $cid_parts));
-    $cache_entry = \Drupal::cache('page')->get($cid);
-    $this->assertIdentical($cache_entry->tags, array('content:1', 'menu:llama'));
-
-    // The "Llama" menu is modified.
-    $menu->label = 'Awesome llama';
-    $menu->save();
-
-    // Verify that after the modified menu, there is a cache miss.
-    $this->drupalGet('test-page');
-    $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
-
-    // Verify a cache hit.
-    $this->drupalGet('test-page');
-    $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT');
-
-    // A link in the "Llama" menu is modified.
-    $menu_link->link_title = 'Guanaco';
-    $menu_link->save();
-
-    // Verify that after the modified menu link, there is a cache miss.
-    $this->drupalGet('test-page');
-    $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
-  }
-
-  /**
    * Tests menu link bundles.
    */
   public function testMenuBundles() {
diff --git a/core/modules/node/lib/Drupal/node/Form/DeleteMultiple.php b/core/modules/node/lib/Drupal/node/Form/DeleteMultiple.php
index 5f474d5..101f18c 100644
--- a/core/modules/node/lib/Drupal/node/Form/DeleteMultiple.php
+++ b/core/modules/node/lib/Drupal/node/Form/DeleteMultiple.php
@@ -7,7 +7,6 @@
 
 namespace Drupal\node\Form;
 
-use Drupal\Core\Cache\Cache;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Form\ConfirmFormBase;
 use Drupal\Component\Utility\String;
@@ -123,7 +122,6 @@ public function submitForm(array &$form, array &$form_state) {
       $count = count($this->nodes);
       watchdog('content', 'Deleted @count posts.', array('@count' => $count));
       drupal_set_message(format_plural($count, 'Deleted 1 post.', 'Deleted @count posts.'));
-      Cache::invalidateTags(array('content' => TRUE));
     }
     $form_state['redirect_route']['route_name'] = 'node.content_overview';
   }
diff --git a/core/modules/node/lib/Drupal/node/Form/NodeDeleteForm.php b/core/modules/node/lib/Drupal/node/Form/NodeDeleteForm.php
index 93148d4..1c1c336 100644
--- a/core/modules/node/lib/Drupal/node/Form/NodeDeleteForm.php
+++ b/core/modules/node/lib/Drupal/node/Form/NodeDeleteForm.php
@@ -7,7 +7,6 @@
 
 namespace Drupal\node\Form;
 
-use Drupal\Core\Cache\Cache;
 use Drupal\Core\Entity\ContentEntityConfirmFormBase;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Routing\UrlGeneratorInterface;
@@ -91,7 +90,6 @@ public function submit(array $form, array &$form_state) {
     $node_type_storage = $this->entityManager->getStorageController('node_type');
     $node_type = $node_type_storage->load($this->entity->bundle())->label();
     drupal_set_message(t('@type %title has been deleted.', array('@type' => $node_type, '%title' => $this->entity->label())));
-    Cache::invalidateTags(array('content' => TRUE));
     $form_state['redirect_route']['route_name'] = '<front>';
   }
 
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeCacheTagsTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeCacheTagsTest.php
new file mode 100644
index 0000000..1ab13c7
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeCacheTagsTest.php
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\Tests\NodeCacheTagsTest.
+ */
+
+namespace Drupal\node\Tests;
+
+use Drupal\system\Tests\Entity\EntityWithUriCacheTagsTestBase;
+
+/**
+ * Tests the Node entity's cache tags.
+ */
+class NodeCacheTagsTest extends EntityWithUriCacheTagsTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = array('node');
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return static::generateStandardizedInfo('node', 'Node');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    // Give anonymous users permission to view nodes, so that we can verify the
+    // cache tags of cached versions of node pages.
+    $user_role = entity_load('user_role', DRUPAL_ANONYMOUS_RID);
+    $user_role->grantPermission('bypass node access');
+    $user_role->save();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static function createEntity() {
+    // Create a "Llama" node.
+    $node = entity_create('node', array(
+      'title' => 'Llama',
+      'type' => 'article',
+    ));
+    $node->save();
+
+    return $node;
+  }
+
+}
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodePageCacheTest.php b/core/modules/node/lib/Drupal/node/Tests/NodePageCacheTest.php
deleted file mode 100644
index 49bc190..0000000
--- a/core/modules/node/lib/Drupal/node/Tests/NodePageCacheTest.php
+++ /dev/null
@@ -1,97 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\node\Tests\NodePageCacheTest.
- */
-
-namespace Drupal\node\Tests;
-
-/**
- * Tests the cache invalidation of node operations.
- */
-class NodePageCacheTest extends NodeTestBase {
-
-  /**
-   * An admin user with administrative permissions for nodes.
-   *
-   * @var \Drupal\user\UserInterface
-   */
-  protected $adminUser;
-
-  public static $modules = array('views');
-
-  public static function getInfo() {
-    return array(
-      'name' => 'Node page cache test',
-      'description' => 'Test cache invalidation of node operations.',
-      'group' => 'Node',
-    );
-  }
-
-  function setUp() {
-    parent::setUp();
-
-    $this->container->get('config.factory')->get('system.performance')
-      ->set('cache.page.use_internal', 1)
-      ->set('cache.page.max_age', 300)
-      ->save();
-
-    $this->adminUser = $this->drupalCreateUser(array(
-      'bypass node access',
-      'access content overview',
-      'administer nodes',
-    ));
-  }
-
-  /**
-   * Tests deleting nodes clears page cache.
-   */
-  public function testNodeDelete() {
-    $author = $this->drupalCreateUser();
-    $node_path = 'node/' . $this->drupalCreateNode(array('uid' => $author->id()))->id();
-
-    // Populate page cache.
-    $this->drupalGet($node_path);
-
-    // Verify the presence of the correct cache tags.
-    $cid_parts = array(url($node_path, array('absolute' => TRUE)), 'html');
-    $cid = sha1(implode(':', $cid_parts));
-    $cache_entry = \Drupal::cache('page')->get($cid);
-    $this->assertIdentical($cache_entry->tags, array('content:1', 'user:' . $author->id(), 'filter_format:plain_text'));
-
-    // Login and delete the node.
-    $this->drupalLogin($this->adminUser);
-    $this->drupalGet($node_path . '/delete');
-    $this->drupalPostForm(NULL, array(), t('Delete'));
-
-    // Logout and check the node is not available.
-    $this->drupalLogout();
-    $this->drupalGet($node_path);
-    $this->assertResponse(404);
-
-    // Create two new nodes.
-    $this->drupalCreateNode();
-    $node_path = 'node/' . $this->drupalCreateNode()->id();
-
-    // Populate page cache.
-    $this->drupalGet($node_path);
-
-    // Login and delete the nodes.
-    $this->drupalLogin($this->adminUser);
-    $this->drupalGet('admin/content');
-    $edit = array(
-      'action' => 'node_delete_action',
-      'node_bulk_form[0]' => 1,
-      'node_bulk_form[1]' => 1,
-    );
-    $this->drupalPostForm(NULL, $edit, t('Apply'));
-    $this->drupalPostForm(NULL, array(), t('Delete'));
-
-    // Logout and check the node is not available.
-    $this->drupalLogout();
-    $this->drupalGet($node_path);
-    $this->assertResponse(404);
-  }
-
-}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Cache/PageCacheTagsTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Cache/PageCacheTagsTestBase.php
new file mode 100644
index 0000000..3bc1ee2
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Cache/PageCacheTagsTestBase.php
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Cache\PageCacheTagsTestBase.
+ */
+
+namespace Drupal\system\Tests\Cache;
+
+use Drupal\simpletest\WebTestBase;
+use Drupal\Component\Utility\String;
+
+/**
+ * Provides helper methods for page cache tags tests.
+ */
+abstract class PageCacheTagsTestBase extends WebTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    // Enable page caching.
+    $config = \Drupal::config('system.performance');
+    $config->set('cache.page.use_internal', 1);
+    $config->set('cache.page.max_age', 300);
+    $config->save();
+  }
+
+  /**
+   * Verify that when loading a given page, it's a page cache hit or miss.
+   *
+   * @param string $path
+   *   The page at this path will be loaded.
+   * @param string $hit_or_miss
+   *   'HIT' if a page cache hit is expected, 'MISS' otherwise.
+   *
+   * @param array|FALSE $tags
+   *   When expecting a page cache hit, you may optionally specify an array of
+   *   expected cache tags. While FALSE, the cache tags will not be verified.
+   */
+  protected function verifyPageCache($path, $hit_or_miss, $tags = FALSE) {
+    $this->drupalGet($path);
+    $message = String::format('Page cache @hit_or_miss for %path.', array('@hit_or_miss' => $hit_or_miss, '%path' => $path));
+    $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), $hit_or_miss, $message);
+
+    if ($hit_or_miss === 'HIT' && is_array($tags)) {
+      $cid_parts = array(url($path, array('absolute' => TRUE)), 'html');
+      $cid = sha1(implode(':', $cid_parts));
+      $cache_entry = \Drupal::cache('page')->get($cid);
+      $this->assertIdentical($cache_entry->tags, $tags);
+    }
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php
new file mode 100644
index 0000000..8dc682b
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php
@@ -0,0 +1,339 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Entity\EntityCacheTagsTestBase.
+ */
+
+namespace Drupal\system\Tests\Entity;
+
+use Drupal\Component\Utility\String;
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\system\Tests\Cache\PageCacheTagsTestBase;
+
+/**
+ * Provides helper methods for Entity cache tags tests.
+ */
+abstract class EntityCacheTagsTestBase extends PageCacheTagsTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('entity_reference', 'entity_test');
+
+  /**
+   * The main entity used for testing.
+   *
+   * @var \Drupal\Core\Entity\EntityInterface
+   */
+  protected $entity;
+
+  /**
+   * The entity instance referencing the main entity.
+   *
+   * @var \Drupal\Core\Entity\EntityInterface
+   */
+  protected $referencing_entity;
+
+  /**
+   * The entity instance not referencing the main entity.
+   *
+   * @var \Drupal\Core\Entity\EntityInterface
+   */
+  protected $non_referencing_entity;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    // Give anonymous users permission to view test entities, so that we can
+    // verify the cache tags of cached versions of test entity pages.
+    $user_role = entity_load('user_role', DRUPAL_ANONYMOUS_RID);
+    $user_role->grantPermission('view test entity');
+    $user_role->save();
+
+    // Create an entity
+    $this->entity = static::createEntity();
+
+    // Create a referencing and a non-referencing entity.
+    list(
+      $this->referencing_entity,
+      $this->non_referencing_entity,
+    ) = static::createReferenceTestEntities($this->entity);
+  }
+
+  /**
+   * Generates the standardized test info.
+   *
+   * @param string $entity_type_id
+   *   The entity type ID whose cache tags to test.
+   * @param string $group
+   *   The test group.
+   *
+   * @return array
+   *
+   * @see \Drupal\simpletest\TestBase::getInfo()
+   */
+  protected static function generateStandardizedInfo($entity_type_id, $group) {
+    $label = \Drupal::entityManager()
+      ->getDefinition($entity_type_id)
+      ->getLabel();
+    return array(
+      'name' => "$label entity cache tags",
+      'description' => "Test the $label entity's cache tags.",
+      'group' => $group,
+    );
+  }
+
+  /**
+   * Creates the entity to be tested.
+   *
+   * @return \Drupal\Core\Entity\EntityInterface
+   *   The entity to be tested.
+   */
+  protected static function createEntity() {
+    // PHP doesn't allow us to declare this method as abstract protected static,
+    // so we simply throw an exception here if this has not been implemented by
+    // a child class.
+    throw new \RuntimeException(String::format('@class must implement \Drupal\system\Tests\Entity\EntityCacheTagsTestBase::createEntity().', array(
+      '@class' => get_called_class(),
+    )));
+  }
+
+  /**
+   * Creates a referencing and a non-referencing entity for testing purposes.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $referenced_entity
+   *  The entity that the referencing entity should reference.
+   *
+   * @return \Drupal\Core\Entity\EntityInterface[]
+   *  An array containing a referencing entity and a non-referencing entity.
+   */
+  protected static function createReferenceTestEntities($referenced_entity) {
+    // All referencing entities should be of the type 'entity_test'.
+    $entity_type = 'entity_test';
+
+    // Create a "foo" bundle for the given entity type.
+    $bundle = 'foo';
+    entity_test_create_bundle($bundle, NULL, $entity_type);
+
+    // Add a field of the given type to the given entity type's "foo" bundle.
+    $field_name = $referenced_entity->getEntityTypeId() . '_reference';
+    entity_create('field_config', array(
+      'name' => $field_name,
+      'entity_type' => $entity_type,
+      'type' => 'entity_reference',
+      'cardinality' => FieldDefinitionInterface::CARDINALITY_UNLIMITED,
+      'settings' => array(
+        'target_type' => $referenced_entity->getEntityTypeId(),
+      ),
+    ))->save();
+    entity_create('field_instance_config', array(
+      'field_name' => $field_name,
+      'entity_type' => $entity_type,
+      'bundle' => $bundle,
+      'settings' => array(
+        'handler' => 'default',
+        'handler_settings' => array(
+          'target_bundles' => array(
+            $referenced_entity->bundle() => $referenced_entity->bundle(),
+          ),
+          'sort' => array('field' => '_none'),
+          'auto_create' => FALSE,
+        ),
+      ),
+    ))->save();
+    entity_get_display($entity_type, $bundle, 'full')
+      ->setComponent($field_name, array('type' => 'entity_reference_label'))
+      ->save();
+
+    // Create an entity that does reference the entity being tested.
+    $label_key = \Drupal::entityManager()->getDefinition($entity_type)->getKey('label');
+    $referencing_entity = entity_create($entity_type, array(
+      $label_key => 'Referencing ' . $entity_type,
+      'status' => 1,
+      'type' => $bundle,
+      $field_name => array('target_id' => $referenced_entity->id()),
+    ));
+    $referencing_entity->save();
+
+    // Create an entity that does not reference the entity being tested.
+    $non_referencing_entity = entity_create($entity_type, array(
+      $label_key => 'Non-referencing ' . $entity_type,
+      'status' => 1,
+      'type' => $bundle,
+    ));
+    $non_referencing_entity->save();
+
+    return array(
+      $referencing_entity,
+      $non_referencing_entity,
+    );
+  }
+
+  /**
+   * Tests cache tags presence and invalidation of the entity when referenced.
+   *
+   * Tests the following cache tags:
+   * - "<entity type>_view:1"
+   * - "<entity type>:<entity ID>"
+   * - "<referencing entity type>_view:1"
+   * * - "<referencing entity type>:<referencing entity ID>"
+   */
+  public function testReferencedEntity() {
+    $entity_type = $this->entity->getEntityTypeId();
+    $referencing_entity_path = $this->referencing_entity->getSystemPath();
+    $referencing_entity_type = $this->referencing_entity->getEntityTypeId();
+    $non_referencing_entity_type = $this->non_referencing_entity->getEntityTypeId();
+    $non_referencing_entity_path = $this->non_referencing_entity->getSystemPath();
+    $listing_path = 'entity_test/list/' . $entity_type . '_reference/' . $entity_type . '/' . $this->entity->id();
+
+    // Generate the standardized entity cache tags.
+    $cache_tag = $entity_type . ':' . $this->entity->id();
+    $view_cache_tag = $entity_type . '_view:1';
+
+    // Generate the cache tags for the (non) referencing entities.
+    $referencing_entity_cache_tags = array(
+      'entity_test_view:1',
+      'entity_test:' . $this->referencing_entity->id(),
+      // Includes the main entity's cache tags, since this entity references it.
+      $cache_tag,
+      $view_cache_tag
+    );
+    $non_referencing_entity_cache_tags = array(
+      'entity_test_view:1',
+      'entity_test:' . $this->non_referencing_entity->id(),
+    );
+
+
+    // Prime the page cache for the referencing entity.
+    $this->verifyPageCache($referencing_entity_path, 'MISS');
+
+    // Verify a cache hit, but also the presence of the correct cache tags.
+    $tags = array_merge(array('content:1'), $referencing_entity_cache_tags);
+    $this->verifyPageCache($referencing_entity_path, 'HIT', $tags);
+
+    // Also verify the existence of an entity render cache entry.
+    $cid = 'entity_view:entity_test:' . $this->referencing_entity->id() . ':full:stark:r.anonymous';
+    $cache_entry = \Drupal::cache()->get($cid);
+    $this->assertIdentical($cache_entry->tags, $referencing_entity_cache_tags);
+
+
+    // Prime the page cache for the non-referencing entity.
+    $this->verifyPageCache($non_referencing_entity_path, 'MISS');
+
+    // Verify a cache hit, but also the presence of the correct cache tags.
+    $tags = array_merge(array('content:1'), $non_referencing_entity_cache_tags);
+    $this->verifyPageCache($non_referencing_entity_path, 'HIT', $tags);
+
+    // Also verify the existence of an entity render cache entry.
+    $cid = 'entity_view:entity_test:' . $this->non_referencing_entity->id() . ':full:stark:r.anonymous';
+    $cache_entry = \Drupal::cache()->get($cid);
+    $this->assertIdentical($cache_entry->tags, $non_referencing_entity_cache_tags);
+
+
+
+    // Prime the page cache for the listing of referencing entities.
+    $this->verifyPageCache($listing_path, 'MISS');
+
+    // Verify a cache hit, but also the presence of the correct cache tags.
+    $tags = array_merge(array('content:1'), $referencing_entity_cache_tags);
+    $this->verifyPageCache($listing_path, 'HIT', $tags);
+
+
+    // Verify that after modifying the referenced entity, there is a cache miss
+    // for both the referencing entity, and the listing of referencing entities,
+    // but not for the non-referencing entity.
+    $this->pass("Test modification of referenced entity.", 'Debug');
+    $this->entity->save();
+    $this->verifyPageCache($referencing_entity_path, 'MISS');
+    $this->verifyPageCache($listing_path, 'MISS');
+    $this->verifyPageCache($non_referencing_entity_path, 'HIT');
+
+    // Verify cache hits.
+    $this->verifyPageCache($referencing_entity_path, 'HIT');
+    $this->verifyPageCache($listing_path, 'HIT');
+
+
+    // Verify that after modifying the referencing entity, there is a cache miss
+    // for both the referencing entity, and the listing of referencing entities,
+    // but not for the non-referencing entity.
+    $this->pass("Test modification of referencing entity.", 'Debug');
+    $this->referencing_entity->save();
+    $this->verifyPageCache($referencing_entity_path, 'MISS');
+    $this->verifyPageCache($listing_path, 'MISS');
+    $this->verifyPageCache($non_referencing_entity_path, 'HIT');
+
+    // Verify cache hits.
+    $this->verifyPageCache($referencing_entity_path, 'HIT');
+    $this->verifyPageCache($listing_path, 'HIT');
+
+
+    // Verify that after modifying the entity's full display, there is a cache
+    // miss for both the referencing entity, and the listing of referencing
+    // entities, but not for the non-referencing entity.
+    $this->pass("Test modification of referenced entity's 'full' display.", 'Debug');
+    $entity_display = entity_get_display($entity_type, $this->entity->bundle(), 'full');
+    $entity_display->save();
+    $this->verifyPageCache($referencing_entity_path, 'MISS');
+    $this->verifyPageCache($listing_path, 'MISS');
+    $this->verifyPageCache($non_referencing_entity_path, 'HIT');
+
+    // Verify cache hits.
+    $this->verifyPageCache($referencing_entity_path, 'HIT');
+    $this->verifyPageCache($listing_path, 'HIT');
+
+
+    // Verify that after invalidating the entity's cache tag directly,  there is
+    // a cache miss for both the referencing entity, and the listing of
+    // referencing entities, but not for the non-referencing entity.
+    $this->pass("Test invalidation of referenced entity's cache tag.", 'Debug');
+    Cache::invalidateTags(array($entity_type => array($this->entity->id())));
+    $this->verifyPageCache($referencing_entity_path, 'MISS');
+    $this->verifyPageCache($listing_path, 'MISS');
+    $this->verifyPageCache($non_referencing_entity_path, 'HIT');
+
+    // Verify cache hits.
+    $this->verifyPageCache($referencing_entity_path, 'HIT');
+    $this->verifyPageCache($listing_path, 'HIT');
+
+
+    // Verify that after invalidating the generic entity type's view cache tag
+    // directly, there is a cache miss for both the referencing entity, and the
+    // listing of referencing entities, but not for the non-referencing entity.
+    $this->pass("Test invalidation of referenced entity's 'view' cache tag.", 'Debug');
+    Cache::invalidateTags(array($entity_type . '_view' => TRUE));
+    $this->verifyPageCache($referencing_entity_path, 'MISS');
+    $this->verifyPageCache($listing_path, 'MISS');
+    $this->verifyPageCache($non_referencing_entity_path, 'HIT');
+
+    // Verify cache hits.
+    $this->verifyPageCache($referencing_entity_path, 'HIT');
+    $this->verifyPageCache($listing_path, 'HIT');
+
+
+    // Verify that after deleting the entity, there is a cache miss for both the
+    // referencing entity, and the listing of referencing entities, but not for
+    // the non-referencing entity.
+    $this->pass('Test deletion of referenced entity.', 'Debug');
+    $this->entity->delete();
+    $this->verifyPageCache($referencing_entity_path, 'MISS');
+    $this->verifyPageCache($listing_path, 'MISS');
+    $this->verifyPageCache($non_referencing_entity_path, 'HIT');
+
+    // Verify cache hits.
+    $tags = array(
+      'content:1',
+      'entity_test_view:1',
+      'entity_test:' . $this->referencing_entity->id(),
+    );
+    $this->verifyPageCache($referencing_entity_path, 'HIT', $tags);
+    $this->verifyPageCache($listing_path, 'HIT', array('content:1'));
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityViewBuilderTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityViewBuilderTest.php
index ab1c086..2d0369b 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityViewBuilderTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityViewBuilderTest.php
@@ -48,7 +48,7 @@ public function testEntityViewBuilderCache() {
     // Test that new entities (before they are saved for the first time) do not
     // generate a cache entry.
     $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
-    $this->assertFalse(isset($build['#cache']), 'The render array element of new (unsaved) entities is not cached.');
+    $this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == array('tags'), 'The render array element of new (unsaved) entities is not cached, but does have cache tags set.');
 
     // Get a fully built entity view render array.
     $entity_test->save();
@@ -144,17 +144,17 @@ public function testEntityViewBuilderCacheToggling() {
     // Test a view mode in default conditions: render caching is enabled for
     // the entity type and the view mode.
     $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
-    $this->assertTrue(isset($build['#cache']), 'A view mode with render cache enabled has the correct output.');
+    $this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == array('tags', 'keys', 'granularity', 'bin') , 'A view mode with render cache enabled has the correct output (cache tags, keys, granularity and bin).');
 
     // Test that a view mode can opt out of render caching.
     $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'test');
-    $this->assertFalse(isset($build['#cache']), 'A view mode with render cache disabled has the correct output.');
+    $this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == array('tags'), 'A view mode with render cache disabled has the correct output (only cache tags).');
 
     // Test that an entity type can opt out of render caching completely.
     $entity_test_no_cache = $this->createTestEntity('entity_test_label');
     $entity_test_no_cache->save();
     $build = $this->container->get('entity.manager')->getViewBuilder('entity_test_label')->view($entity_test_no_cache, 'full');
-    $this->assertFalse(isset($build['#cache']), 'An entity type can opt out of render caching regardless of view mode configuration.');
+    $this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == array('tags'), 'An entity type can opt out of render caching regardless of view mode configuration, but always has cache tags set.');
   }
 
   /**
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityWithUriCacheTagsTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityWithUriCacheTagsTestBase.php
new file mode 100644
index 0000000..7472ce3
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityWithUriCacheTagsTestBase.php
@@ -0,0 +1,96 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Entity\EntityWithUriCacheTagsTestBase.
+ */
+
+namespace Drupal\system\Tests\Entity;
+
+use Drupal\Core\Cache\Cache;
+use Drupal\system\Tests\Entity\EntityCacheTagsTestBase;
+
+/**
+ * Provides helper methods for Entity cache tags tests; for entities with URIs.
+ */
+abstract class EntityWithUriCacheTagsTestBase extends EntityCacheTagsTestBase {
+
+  /**
+   * Tests cache tags presence and invalidation of the entity at its URI.
+   *
+   * Tests the following cache tags:
+   * - "<entity type>_view:1"
+   * - "<entity_type>:<entity ID>"
+   */
+  public function testEntityUri() {
+    $entity_path = $this->entity->getSystemPath();
+    $entity_type = $this->entity->getEntityTypeId();
+
+    // Generate the standardized entity cache tags.
+    $cache_tag = $entity_type . ':' . $this->entity->id();
+    $view_cache_tag = $entity_type . '_view:1';
+
+
+    // Prime the page cache.
+    $this->verifyPageCache($entity_path, 'MISS');
+
+    // Verify a cache hit, but also the presence of the correct cache tags.
+    $tags = array('content:1', $view_cache_tag, $cache_tag);
+    $this->verifyPageCache($entity_path, 'HIT');
+
+    // Also verify the existence of an entity render cache entry, if this entity
+    // type supports render caching.
+    if (\Drupal::entityManager()->getDefinition($entity_type)->isRenderCacheable()) {
+      $cid = 'entity_view:' . $entity_type . ':' . $this->entity->id() . ':full:stark:r.anonymous';
+      $cache_entry = \Drupal::cache()->get($cid);
+      $this->assertIdentical($cache_entry->tags, array($view_cache_tag, $cache_tag));
+    }
+
+    // Verify that after modifying the entity, there is a cache miss.
+    $this->pass("Test modification of entity.", 'Debug');
+    $this->entity->save();
+    $this->verifyPageCache($entity_path, 'MISS');
+
+    // Verify a cache hit.
+    $this->verifyPageCache($entity_path, 'HIT');
+
+
+    // Verify that after modifying the entity's "full" display, there is a cache
+    // miss.
+    $this->pass("Test modification of entity's 'full' display.", 'Debug');
+    $entity_display = entity_get_display($entity_type, $this->entity->bundle(), 'full');
+    $entity_display->save();
+    $this->verifyPageCache($entity_path, 'MISS');
+
+    // Verify a cache hit.
+    $this->verifyPageCache($entity_path, 'HIT');
+
+
+    // Verify that after invalidating the entity's cache tag directly, there is
+    // a cache miss.
+    $this->pass("Test invalidation of entity's cache tag.", 'Debug');
+    Cache::invalidateTags(array($entity_type => array($this->entity->id())));
+    $this->verifyPageCache($entity_path, 'MISS');
+
+    // Verify a cache hit.
+    $this->verifyPageCache($entity_path, 'HIT');
+
+
+    // Verify that after invalidating the generic entity type's view cache tag
+    // directly, there is a cache miss.
+    $this->pass("Test invalidation of entity's 'view' cache tag.", 'Debug');
+    Cache::invalidateTags(array($entity_type . '_view' => TRUE));
+    $this->verifyPageCache($entity_path, 'MISS');
+
+    // Verify a cache hit.
+    $this->verifyPageCache($entity_path, 'HIT');
+
+
+    // Verify that after deleting the entity, there is a cache miss.
+    $this->pass('Test deletion of entity.', 'Debug');
+    $this->entity->delete();
+    $this->verifyPageCache($entity_path, 'MISS');
+    $this->assertResponse(404);
+  }
+
+}
diff --git a/core/modules/system/tests/modules/entity_test/entity_test.routing.yml b/core/modules/system/tests/modules/entity_test/entity_test.routing.yml
index 6905f23..55cc8bf 100644
--- a/core/modules/system/tests/modules/entity_test/entity_test.routing.yml
+++ b/core/modules/system/tests/modules/entity_test/entity_test.routing.yml
@@ -23,5 +23,13 @@ entity_test.render_no_view_mode:
   requirements:
     _access: 'TRUE'
 
+entity_test.list_referencing_entities:
+  path: '/entity_test/list/{entity_reference_field_name}/{referenced_entity_type}/{referenced_entity_id}'
+  defaults:
+    _content: '\Drupal\entity_test\Controller\EntityTestController::listReferencingEntities'
+    _title: 'List entity_test entities referencing the given entity'
+  requirements:
+    _access: 'TRUE'
+
 route_callbacks:
   - '\Drupal\entity_test\Routing\EntityTestRoutes::routes'
diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Controller/EntityTestController.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Controller/EntityTestController.php
index a6a42ee..126f08b 100644
--- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Controller/EntityTestController.php
+++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Controller/EntityTestController.php
@@ -37,4 +37,32 @@ public function testAdmin() {
     return '';
   }
 
+  /**
+   * List entity_test entities referencing the given entity
+   *
+   * @param string $entity_reference_field_name
+   *   The name of the entity_reference field to use in the query.
+   * @param string $referenced_entity_type
+   *   The type of the entity being referenced.
+   * @param int $referenced_entity_id
+   *   The ID of the entity being referenced.
+   *
+   * @return array
+   *   A renderable array.
+   */
+  public function listReferencingEntities($entity_reference_field_name, $referenced_entity_type, $referenced_entity_id) {
+    // Early return if the referenced entity does not exist (or is deleted).
+    if (entity_load($referenced_entity_type, $referenced_entity_id) === NULL) {
+      return array();
+    }
+
+    $query = \Drupal::service('entity.query')
+      ->get('entity_test')
+      ->condition($entity_reference_field_name . '.target_id', $referenced_entity_id);
+
+    $entities = entity_load_multiple('entity_test', $query->execute());
+
+    return entity_view_multiple($entities, 'full');
+  }
+
 }
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermCacheTagsTest.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermCacheTagsTest.php
new file mode 100644
index 0000000..627da44
--- /dev/null
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermCacheTagsTest.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\taxonomy\Tests\TermCacheTagsTest.
+ */
+
+namespace Drupal\taxonomy\Tests;
+
+use Drupal\system\Tests\Entity\EntityWithUriCacheTagsTestBase;
+
+/**
+ * Tests the Taxonomy term entity's cache tags.
+ */
+class TermCacheTagsTest extends EntityWithUriCacheTagsTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = array('taxonomy');
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return static::generateStandardizedInfo('taxonomy_term', 'Taxonomy');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static function createEntity() {
+    // Create a "Camelids" vocabulary.
+    $vocabulary = entity_create('taxonomy_vocabulary',  array(
+      'name' => 'Camelids',
+      'vid' => 'camelids',
+    ));
+    $vocabulary->save();
+
+    // Create a "Llama" taxonomy term.
+    $term = entity_create('taxonomy_term', array(
+      'name' => 'Llama',
+      'vid' => $vocabulary->id(),
+    ));
+    $term->save();
+
+    return $term;
+  }
+
+}
diff --git a/core/modules/user/lib/Drupal/user/Tests/UserCacheTagsTest.php b/core/modules/user/lib/Drupal/user/Tests/UserCacheTagsTest.php
new file mode 100644
index 0000000..d9e1c8f
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/Tests/UserCacheTagsTest.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Tests\UserCacheTagsTest.
+ */
+
+namespace Drupal\user\Tests;
+
+use Drupal\system\Tests\Entity\EntityWithUriCacheTagsTestBase;
+
+/**
+ * Tests the User entity's cache tags.
+ */
+class UserCacheTagsTest extends EntityWithUriCacheTagsTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = array('user');
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return static::generateStandardizedInfo('user', 'User');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    // Give anonymous users permission to view user profiles, so that we can
+    // verify the cache tags of cached versions of user profile pages.
+    $user_role = entity_load('user_role', DRUPAL_ANONYMOUS_RID);
+    $user_role->grantPermission('access user profiles');
+    $user_role->save();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static function createEntity() {
+    // Create a "Llama" user.
+    $user = entity_create('user', array(
+      'name' => 'Llama',
+    ));
+    $user->save();
+
+    return $user;
+  }
+
+}
