 core/lib/Drupal/Core/Entity/EntityViewBuilder.php  |   12 +-
 .../Tests/CustomBlockCacheTagsTest.php             |   44 +++
 .../Drupal/comment/Tests/CommentCacheTagsTest.php  |   74 ++++
 .../field/lib/Drupal/field/Entity/FieldConfig.php  |    7 +
 .../Drupal/field/Entity/FieldInstanceConfig.php    |    7 +
 .../lib/Drupal/menu/Tests/MenuCacheTagsTest.php    |  116 ++++++
 .../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    |   69 ++++
 .../lib/Drupal/node/Tests/NodePageCacheTest.php    |   98 -----
 core/modules/simpletest/css/simpletest.module.css  |    4 +
 .../simpletest/Form/SimpletestResultsForm.php      |   47 +--
 .../lib/Drupal/simpletest/Tests/SimpleTestTest.php |    8 +-
 .../system/Tests/Cache/PageCacheTagsTestBase.php   |   56 +++
 .../Tests/Entity/EntityCacheTagsTestBase.php       |  379 ++++++++++++++++++++
 .../system/Tests/Entity/EntityViewBuilderTest.php  |    8 +-
 .../Entity/EntityWithUriCacheTagsTestBase.php      |  152 ++++++++
 .../modules/entity_test/entity_test.routing.yml    |    8 +
 .../Controller/EntityTestController.php            |   64 +++-
 .../lib/Drupal/taxonomy/Entity/Vocabulary.php      |    4 +
 .../Drupal/taxonomy/Tests/TermCacheTagsTest.php    |   50 +++
 .../taxonomy/VocabularyStorageController.php       |    3 +-
 .../lib/Drupal/user/Tests/UserCacheTagsTest.php    |   55 +++
 24 files changed, 1130 insertions(+), 199 deletions(-)

diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
index 7f86532..af628c2 100644
--- a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
+++ b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
@@ -144,19 +144,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() && $entity->isDefaultRevision() && $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()),
-        ),
       );
 
       if ($entity instanceof TranslatableInterface && count($entity->getTranslationLanguages()) > 1) {
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..6270631
--- /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..d42ec20
--- /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/field/lib/Drupal/field/Entity/FieldConfig.php b/core/modules/field/lib/Drupal/field/Entity/FieldConfig.php
index 503fe14..0cb62ff 100644
--- a/core/modules/field/lib/Drupal/field/Entity/FieldConfig.php
+++ b/core/modules/field/lib/Drupal/field/Entity/FieldConfig.php
@@ -378,6 +378,13 @@ protected function preSaveUpdated(EntityStorageControllerInterface $storage_cont
   public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
     // Clear the cache.
     field_cache_clear();
+
+    // Invalidate the render cache for all affected entities.
+    $entity_manager = \Drupal::entityManager();
+    $entity_type = $this->getTargetEntityTypeId();
+    if ($entity_manager->hasController($entity_type, 'view_builder')) {
+      $entity_manager->getViewBuilder($entity_type)->resetCache();
+    }
   }
 
   /**
diff --git a/core/modules/field/lib/Drupal/field/Entity/FieldInstanceConfig.php b/core/modules/field/lib/Drupal/field/Entity/FieldInstanceConfig.php
index 6024061..c398ffc 100644
--- a/core/modules/field/lib/Drupal/field/Entity/FieldInstanceConfig.php
+++ b/core/modules/field/lib/Drupal/field/Entity/FieldInstanceConfig.php
@@ -368,6 +368,13 @@ public function preSave(EntityStorageControllerInterface $storage_controller) {
   public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
     // Clear the cache.
     field_cache_clear();
+
+    // Invalidate the render cache for all affected entities.
+    $entity_manager = \Drupal::entityManager();
+    $entity_type = $this->getTargetEntityTypeId();
+    if ($entity_manager->hasController($entity_type, 'view_builder')) {
+      $entity_manager->getViewBuilder($entity_type)->resetCache();
+    }
   }
 
   /**
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..c09875c
--- /dev/null
+++ b/core/modules/menu/lib/Drupal/menu/Tests/MenuCacheTagsTest.php
@@ -0,0 +1,116 @@
+<?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() {
+    $path = 'test-page';
+
+    // 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($path, 'MISS');
+
+    // Verify a cache hit, but also the presence of the correct cache tags.
+    $this->verifyPageCache($path, '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($path, 'MISS');
+
+    // Verify a cache hit.
+    $this->verifyPageCache($path, '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($path, 'MISS');
+
+    // Verify a cache hit.
+    $this->verifyPageCache($path, '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($path, 'MISS');
+
+    // Verify a cache hit.
+    $this->verifyPageCache($path, '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($path, 'MISS');
+
+    // Verify a cache hit.
+    $this->verifyPageCache($path, '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($path, 'MISS');
+
+    // Verify a cache hit.
+    $this->verifyPageCache($path, '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..514c2d9
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeCacheTagsTest.php
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\Tests\NodeCacheTagsTest.
+ */
+
+namespace Drupal\node\Tests;
+
+use Drupal\Core\Entity\EntityInterface;
+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('acess content');
+    $user_role->save();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static function createEntity() {
+    // Create a "Camelids" node type.
+    entity_create('node_type', array(
+      'name' => 'Camelids',
+      'type' => 'camelids',
+    ))->save();
+
+    // Create a "Llama" node.
+    $node = entity_create('node', array('type' => 'camelids'));
+    $node->setTitle('Llama')
+      ->setPublished(TRUE)
+      ->save();
+
+    return $node;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static function getAdditionalCacheTagsForEntity(EntityInterface $node) {
+    return array('user:' . $node->getOwnerId());
+  }
+
+}
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 0fc6ed2..0000000
--- a/core/modules/node/lib/Drupal/node/Tests/NodePageCacheTest.php
+++ /dev/null
@@ -1,98 +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_id = $this->drupalCreateNode(array('uid' => $author->id()))->id();
-    $node_path = 'node/' . $node_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', 'node_view:' . $node_id,  'node:' . $node_id, '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/simpletest/css/simpletest.module.css b/core/modules/simpletest/css/simpletest.module.css
index 611cc4a..0a1b776 100644
--- a/core/modules/simpletest/css/simpletest.module.css
+++ b/core/modules/simpletest/css/simpletest.module.css
@@ -39,6 +39,10 @@ div.message > div.item-list {
   font-weight: normal;
 }
 
+details > summary div[class*="simpletest-"] {
+  display: inline-block;
+  margin: 0 .6em;
+}
 div.simpletest-pass {
   color: #33a333;
 }
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/Form/SimpletestResultsForm.php b/core/modules/simpletest/lib/Drupal/simpletest/Form/SimpletestResultsForm.php
index 8b74dc5..2be957d 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/Form/SimpletestResultsForm.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/Form/SimpletestResultsForm.php
@@ -115,17 +115,16 @@ public function buildForm(array $form, array &$form_state, $test_id = NULL) {
     );
 
     // Summary result widget.
-    $form['result'] = array(
-      '#type' => 'fieldset',
-      '#title' => $this->t('Results'),
-    );
-    $form['result']['summary'] = $summary = array(
+    $summary = array(
       '#theme' => 'simpletest_result_summary',
       '#pass' => 0,
       '#fail' => 0,
       '#exception' => 0,
       '#debug' => 0,
     );
+    $form['summary'] = $summary + array(
+      '#weight' => -110,
+    );
 
     simpletest_classloader_register();
 
@@ -148,8 +147,7 @@ public function buildForm(array $form, array &$form_state, $test_id = NULL) {
         '#open' => TRUE,
         '#description' => $info['description'],
       );
-      $form['result']['results'][$group]['summary'] = $summary;
-      $group_summary =& $form['result']['results'][$group]['summary'];
+      $group_summary = $summary;
 
       // Create table of assertions for the group.
       $rows = array();
@@ -169,7 +167,7 @@ public function buildForm(array $form, array &$form_state, $test_id = NULL) {
         $rows[] = array('data' => $row, 'class' => array($class));
 
         $group_summary['#' . $assertion->status]++;
-        $form['result']['summary']['#' . $assertion->status]++;
+        $form['summary']['#' . $assertion->status]++;
       }
       $form['result']['results'][$group]['table'] = array(
         '#theme' => 'table',
@@ -179,6 +177,8 @@ public function buildForm(array $form, array &$form_state, $test_id = NULL) {
 
       // Set summary information.
       $group_summary['#ok'] = $group_summary['#fail'] + $group_summary['#exception'] == 0;
+
+      $form['result']['results'][$group]['#title'] .= drupal_render($group_summary);
       $form['result']['results'][$group]['#open'] = !$group_summary['#ok'];
 
       // Store test group (class) as for use in filter.
@@ -186,18 +186,17 @@ public function buildForm(array $form, array &$form_state, $test_id = NULL) {
     }
 
     // Overall summary status.
-    $form['result']['summary']['#ok'] = $form['result']['summary']['#fail'] + $form['result']['summary']['#exception'] == 0;
+    $form['summary']['#ok'] = $form['summary']['#fail'] + $form['summary']['#exception'] == 0;
 
     // Actions.
     $form['#action'] = url('admin/config/development/testing/results/re-run');
-    $form['action'] = array(
-      '#type' => 'fieldset',
-      '#title' => $this->t('Actions'),
+    $form['actions'] = array(
+      '#type' => 'actions',
       '#attributes' => array('class' => array('container-inline')),
-      '#weight' => -11,
+      '#weight' => -100,
     );
 
-    $form['action']['filter'] = array(
+    $form['actions']['filter'] = array(
       '#type' => 'select',
       '#title' => 'Filter',
       '#options' => array(
@@ -206,28 +205,34 @@ public function buildForm(array $form, array &$form_state, $test_id = NULL) {
         'fail' => $this->t('Fail (@count)', array('@count' => count($filter['fail']))),
       ),
     );
-    $form['action']['filter']['#default_value'] = ($filter['fail'] ? 'fail' : 'all');
+    $form['actions']['filter']['#default_value'] = ($filter['fail'] ? 'fail' : 'all');
 
     // Categorized test classes for to be used with selected filter value.
-    $form['action']['filter_pass'] = array(
+    $form['actions']['filter_pass'] = array(
       '#type' => 'hidden',
       '#default_value' => implode(',', $filter['pass']),
     );
-    $form['action']['filter_fail'] = array(
+    $form['actions']['filter_fail'] = array(
       '#type' => 'hidden',
       '#default_value' => implode(',', $filter['fail']),
     );
 
-    $form['action']['op'] = array(
+    $form['actions']['run'] = array(
       '#type' => 'submit',
       '#value' => $this->t('Run tests'),
     );
-
-    $form['action']['return'] = array(
+    $form['actions']['return'] = array(
       '#type' => 'link',
       '#title' => $this->t('Return to list'),
       '#href' => 'admin/config/development/testing',
+      '#attributes' => array('class' => array('button')),
     );
+    if ($filter['fail']) {
+      $form['actions']['run']['#button_type'] = 'primary';
+    }
+    else {
+      $form['actions']['return']['#attributes']['class'][] = 'button--primary';
+    }
 
     if (is_numeric($test_id)) {
       simpletest_clean_results_table($test_id);
@@ -261,7 +266,7 @@ public function submitForm(array &$form, array &$form_state) {
     $form_execute = array();
     $form_state_execute = array('values' => array());
     foreach ($classes as $class) {
-      $form_state_execute['values'][$class] = 1;
+      $form_state_execute['values']['tests'][$class] = $class;
     }
 
     // Submit the simpletest test form to rerun the tests.
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/Tests/SimpleTestTest.php b/core/modules/simpletest/lib/Drupal/simpletest/Tests/SimpleTestTest.php
index 488b9a7..5c328ec 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/Tests/SimpleTestTest.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/Tests/SimpleTestTest.php
@@ -233,7 +233,7 @@ function confirmStubTestResults() {
 
     $this->assertAssertion("Debug: 'Foo'", 'Debug', 'Fail', 'SimpleTestTest.php', 'Drupal\simpletest\Tests\SimpleTestTest->stubTest()');
 
-    $this->assertEqual('6 passes, 5 fails, 2 exceptions, 1 debug message', $this->childTestResults['summary'], 'Stub test summary is correct');
+    $this->assertEqual('6 passes, 3 fails, 2 exceptions, 1 debug message', $this->childTestResults['summary']);
 
     $this->test_ids[] = $test_id = $this->getTestIdFromResults();
     $this->assertTrue($test_id, 'Found test ID in results.');
@@ -286,8 +286,8 @@ function getTestResults() {
     if ($this->parse()) {
       if ($details = $this->getResultFieldSet()) {
         // Code assumes this is the only test in group.
-        $results['summary'] = $this->asText($details->div->div[1]);
-        $results['name'] = $this->asText($details->summary);
+        $results['summary'] = trim((string) $details->summary->div);
+        $results['name'] = trim((string) $details->summary);
 
         $results['assertions'] = array();
         $tbody = $details->div->table->tbody;
@@ -314,7 +314,7 @@ function getResultFieldSet() {
     $all_details = $this->xpath('//details');
     $info = $this->getInfo();
     foreach ($all_details as $details) {
-      if ($this->asText($details->summary) == $info['name']) {
+      if ($info['name'] == trim((string) $details->summary)) {
         return $details;
       }
     }
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..194976a
--- /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', 3600);
+    $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..a83ec32
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php
@@ -0,0 +1,379 @@
+<?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', 'field_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();
+
+    // If this is a fieldable entity, then add a configurable field. We will use
+    // this configurable field in later tests to ensure that modifications to
+    // field (instance) configuration invalidate render cache entries.
+    if ($this->entity->getEntityType()->isFieldable()) {
+      // add field, so we can modify the Field and FieldInstance entities to
+      // verify that changes to those indeed clear cache tags
+      $field_name = drupal_strtolower($this->randomName());
+      $this->field = entity_create('field_config', array(
+        'name' => 'configurable_field',
+        'entity_type' => $this->entity->getEntityTypeId(),
+        'type' => 'test_field',
+        'settings' => array(),
+      ));
+      $this->field->save();
+
+      $this->instance = entity_create('field_instance_config', array(
+        'entity_type' => $this->entity->getEntityTypeId(),
+        'bundle' => $this->entity->bundle(),
+        'field_name' => 'configurable_field',
+        'label' => 'Configurable field',
+        'settings' => array(),
+      ));
+      $this->instance->save();
+
+      // Reload the entity now that a new field has been added to it.
+      $storage_controller = $this->container
+        ->get('entity.manager')
+        ->getStorageController($this->entity->getEntityTypeId());
+      $storage_controller->resetCache();
+      $this->entity = $storage_controller->load($this->entity->id());
+    }
+
+    // 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_label
+   *   The label of the entity type whose cache tags to test.
+   * @param string $group
+   *   The test group.
+   *
+   * @return array
+   *
+   * @see \Drupal\simpletest\TestBase::getInfo()
+   */
+  protected static function generateStandardizedInfo($entity_type_label, $group) {
+    return array(
+      'name' => "$entity_type_label entity cache tags",
+      'description' => "Test the $entity_type_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();
+    $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 non-referencing entity, there is a cache
+    // miss for only the non-referencing entity, not for the referencing entity,
+    // nor for the listing of referencing entities.
+    $this->pass("Test modification of non-referencing entity.", 'Debug');
+    $this->non_referencing_entity->save();
+    $this->verifyPageCache($referencing_entity_path, 'HIT');
+    $this->verifyPageCache($listing_path, 'HIT');
+    $this->verifyPageCache($non_referencing_entity_path, 'MISS');
+
+    // Verify cache hits.
+    $this->verifyPageCache($non_referencing_entity_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..a979174
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityWithUriCacheTagsTestBase.php
@@ -0,0 +1,152 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Entity\EntityWithUriCacheTagsTestBase.
+ */
+
+namespace Drupal\system\Tests\Entity;
+
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\system\Tests\Entity\EntityCacheTagsTestBase;
+
+/**
+ * Provides helper methods for Entity cache tags tests; for entities with URIs.
+ */
+abstract class EntityWithUriCacheTagsTestBase extends EntityCacheTagsTestBase {
+
+  /**
+   * Returns the additional (non-standard) cache tags for the tested entity.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity to be tested, as created by createEntity().
+   * @return array
+   *   An array of the additional cache tags.
+   *
+   * @see \Drupal\system\Tests\Entity\EntityCacheTagsTestBase::createEntity()
+   */
+  protected static function getAdditionalCacheTagsForEntity(EntityInterface $entity) {
+    return array();
+  }
+
+  /**
+   * 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);
+      $expected_cache_tags = array_merge(array($view_cache_tag, $cache_tag), static::getAdditionalCacheTagsForEntity($this->entity));
+      $this->assertIdentical($cache_entry->tags, $expected_cache_tags);
+    }
+
+    // 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');
+
+
+    $bundle_entity_type = $this->entity->getEntityType()->getBundleEntityType();
+    if ($bundle_entity_type !== 'bundle') {
+      // Verify that after modifying the corresponding bundle entity, there is a
+      // cache miss.
+      $this->pass("Test modification of entity's bundle entity.", 'Debug');
+      $bundle_entity = entity_load($bundle_entity_type, $this->entity->bundle());
+      $bundle_entity->save();
+      $this->verifyPageCache($entity_path, 'MISS');
+
+      // Verify a cache hit.
+      $this->verifyPageCache($entity_path, 'HIT');
+    }
+
+
+    if ($this->entity->getEntityType()->isFieldable()) {
+      // Verify that after modifying a configurable field on the entity, there
+      // is a cache miss.
+      $this->pass("Test modification of entity's configurable field.", 'Debug');
+      $field_name = $this->entity->getEntityTypeId() . '.configurable_field';
+      $field = entity_load('field_config', $field_name);
+      $field->save();
+      $this->verifyPageCache($entity_path, 'MISS');
+
+      // Verify a cache hit.
+      $this->verifyPageCache($entity_path, 'HIT');
+
+
+      // Verify that after modifying a configurable field instance on the
+      // entity, there is a cache miss.
+      $this->pass("Test modification of entity's configurable field instance.", 'Debug');
+      $field_instance_name = $this->entity->getEntityTypeId() . '.' . $this->entity->bundle() . '.configurable_field';
+      $field_instance = entity_load('field_instance_config', $field_instance_name);
+      $field_instance->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..77b5851 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
@@ -7,13 +7,42 @@
 
 namespace Drupal\entity_test\Controller;
 
+use Drupal\Core\Controller\ControllerBase;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\Query\QueryFactory;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Controller routines for entity_test routes.
  */
-class EntityTestController {
+class EntityTestController extends ControllerBase {
+
+  /**
+   * The entity query factory.
+   *
+   * @var \Drupal\Core\Entity\Query\QueryFactory
+   */
+  protected $entityQueryFactory;
+
+  /**
+   * Constructs a new EntityTestController.
+   *
+   * @param \Drupal\Core\Entity\Query\QueryFactory
+   *   The entity query factory.
+   */
+  public function __construct(QueryFactory $entity_query_factory) {
+    $this->entityQueryFactory = $entity_query_factory;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('entity.query')
+    );
+  }
 
   /**
    * @todo Remove entity_test_add()
@@ -37,4 +66,37 @@ 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).
+    $referenced_entity = $this->entityManager()
+      ->getStorageController($referenced_entity_type)
+      ->load($referenced_entity_id);
+    if ($referenced_entity === NULL) {
+      return array();
+    }
+
+    $query = $this->entityQueryFactory
+      ->get('entity_test')
+      ->condition($entity_reference_field_name . '.target_id', $referenced_entity_id);
+    $entities = $this->entityManager()
+      ->getStorageController('entity_test')
+      ->loadMultiple($query->execute());
+    return $this->entityManager()
+      ->getViewBuilder('entity_test')
+      ->viewMultiple($entities, 'full');
+  }
+
 }
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Vocabulary.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Vocabulary.php
index 6a0132a..c7f2f14 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Vocabulary.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Vocabulary.php
@@ -129,6 +129,10 @@ public function postSave(EntityStorageControllerInterface $storage_controller, $
       // Update bundles.
       entity_invoke_bundle_hook('rename', 'taxonomy_term', $this->getOriginalId(), $this->id());
     }
+    else {
+      // Invalidate the render cache for all terms.
+      \Drupal::entityManager()->getViewBuilder('taxonomy_term')->resetCache();
+    }
     $storage_controller->resetCache($update ? array($this->getOriginalId()) : array());
   }
 
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..6d7b649
--- /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/taxonomy/lib/Drupal/taxonomy/VocabularyStorageController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyStorageController.php
index e98c36f..249b0b8 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyStorageController.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyStorageController.php
@@ -16,12 +16,11 @@
 class VocabularyStorageController extends ConfigStorageController implements VocabularyStorageControllerInterface {
 
   /**
-   * Overrides Drupal\Core\Config\Entity\ConfigStorageController::resetCache().
+   * {@inheritdoc}
    */
   public function resetCache(array $ids = NULL) {
     drupal_static_reset('taxonomy_vocabulary_get_names');
     parent::resetCache($ids);
-    Cache::invalidateTags(array('content' => TRUE));
     entity_info_cache_clear();
   }
 
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..8db5b7b
--- /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;
+  }
+
+}
