diff --git a/core/modules/action/action.views.inc b/core/modules/action/action.views.inc
index e933392..9bdb3d3 100644
--- a/core/modules/action/action.views.inc
+++ b/core/modules/action/action.views.inc
@@ -27,19 +27,3 @@ function action_views_data() {
   );
   return $data;
 }
-
-/**
- * Implements hook_views_form_substitutions().
- */
-function action_views_form_substitutions() {
-  // Views check_plain()s the column label, so we need to match that.
-  $select_all_placeholder = check_plain('<!--action-bulk-form-select-all-->');
-  $select_all = array(
-    '#type' => 'checkbox',
-    '#default_value' => FALSE,
-    '#attributes' => array('class' => array('action-table-select-all')),
-  );
-  return array(
-    $select_all_placeholder => drupal_render($select_all),
-  );
-}
diff --git a/core/modules/action/action.views_runtime.inc b/core/modules/action/action.views_runtime.inc
new file mode 100644
index 0000000..859cffd
--- /dev/null
+++ b/core/modules/action/action.views_runtime.inc
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * @file
+ * Provides views runtime hooks for action.module.
+ */
+
+/**
+ * Implements hook_views_form_substitutions().
+ */
+function action_views_form_substitutions() {
+  // Views check_plain()s the column label, so we need to match that.
+  $select_all_placeholder = check_plain('<!--action-bulk-form-select-all-->');
+  $select_all = array(
+    '#type' => 'checkbox',
+    '#default_value' => FALSE,
+    '#attributes' => array('class' => array('action-table-select-all')),
+  );
+  return array(
+    $select_all_placeholder => drupal_render($select_all),
+  );
+}
diff --git a/core/modules/node/node.views.inc b/core/modules/node/node.views.inc
index cd6575e..5ac6729 100644
--- a/core/modules/node/node.views.inc
+++ b/core/modules/node/node.views.inc
@@ -667,64 +667,6 @@ function node_row_node_view_preprocess_node(&$vars) {
 }
 
 /**
- * Implements hook_views_query_substitutions().
- */
-function node_views_query_substitutions() {
-  return array(
-    '***ADMINISTER_NODES***' => intval(user_access('administer nodes')),
-    '***VIEW_OWN_UNPUBLISHED_NODES***' => intval(user_access('view own unpublished content')),
-    '***BYPASS_NODE_ACCESS***' =>  intval(user_access('bypass node access')),
-  );
-}
-
-/**
- * Implements hook_views_analyze().
- */
-function node_views_analyze(ViewExecutable $view) {
-  $ret = array();
-  // Check for something other than the default display:
-  if ($view->storage->get('base_table') == 'node') {
-    foreach ($view->displayHandlers as $id => $display) {
-      if (!$display->isDefaulted('access') || !$display->isDefaulted('filters')) {
-        // check for no access control
-        $access = $display->getOption('access');
-        if (empty($access['type']) || $access['type'] == 'none') {
-          $result = db_select('role_permission', 'p')
-            ->fields('p', array('rid', 'permission'))
-            ->condition('p.rid', array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID), 'IN')
-            ->condition('p.permission', 'access content')
-            ->execute();
-
-          foreach ($result as $role) {
-            $role->safe = TRUE;
-            $roles[$role->rid] = $role;
-          }
-          if (!($roles[DRUPAL_ANONYMOUS_RID]->safe && $roles[DRUPAL_AUTHENTICATED_RID]->safe)) {
-            $ret[] = Analyzer::formatMessage(t('Some roles lack permission to access content, but display %display has no access control.', array('%display' => $display->display['display_title'])), 'warning');
-          }
-          $filters = $display->getOption('filters');
-          foreach ($filters as $filter) {
-            if ($filter['table'] == 'node' && ($filter['field'] == 'status' || $filter['field'] == 'status_extra')) {
-              continue 2;
-            }
-          }
-          $ret[] = Analyzer::formatMessage(t('Display %display has no access control but does not contain a filter for published nodes.', array('%display' => $display->display['display_title'])), 'warning');
-        }
-      }
-    }
-  }
-  foreach ($view->displayHandlers as $display) {
-    if ($display->getPluginId() == 'page') {
-      if ($display->getOption('path') == 'node/%') {
-        $ret[] = Analyzer::formatMessage(t('Display %display has set node/% as path. This will not produce what you want. If you want to have multiple versions of the node view, use panels.', array('%display' => $display->display['display_title'])), 'warning');
-      }
-    }
-  }
-
-  return $ret;
-}
-
-/**
  * Implements hook_views_wizard().
  */
 function node_views_wizard() {
diff --git a/core/modules/node/node.views_runtime.inc b/core/modules/node/node.views_runtime.inc
new file mode 100644
index 0000000..ead8702
--- /dev/null
+++ b/core/modules/node/node.views_runtime.inc
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * @file
+ * Provide views runtime hooks for node.module.
+ */
+
+use Drupal\views\ViewExecutable;
+
+/**
+ * Implements hook_views_query_substitutions().
+ */
+function node_views_query_substitutions(ViewExecutable $view) {
+  return array(
+    '***ADMINISTER_NODES***' => intval(user_access('administer nodes')),
+    '***VIEW_OWN_UNPUBLISHED_NODES***' => intval(user_access('view own unpublished content')),
+    '***BYPASS_NODE_ACCESS***' =>  intval(user_access('bypass node access')),
+  );
+}
+
+/**
+ * Implements hook_views_analyze().
+ */
+function node_views_analyze(ViewExecutable $view) {
+  $ret = array();
+  // Check for something other than the default display:
+  if ($view->storage->get('base_table') == 'node') {
+    foreach ($view->displayHandlers as $id => $display) {
+      if (!$display->isDefaulted('access') || !$display->isDefaulted('filters')) {
+        // check for no access control
+        $access = $display->getOption('access');
+        if (empty($access['type']) || $access['type'] == 'none') {
+          $result = db_select('role_permission', 'p')
+            ->fields('p', array('rid', 'permission'))
+            ->condition('p.rid', array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID), 'IN')
+            ->condition('p.permission', 'access content')
+            ->execute();
+
+          foreach ($result as $role) {
+            $role->safe = TRUE;
+            $roles[$role->rid] = $role;
+          }
+          if (!($roles[DRUPAL_ANONYMOUS_RID]->safe && $roles[DRUPAL_AUTHENTICATED_RID]->safe)) {
+            $ret[] = Analyzer::formatMessage(t('Some roles lack permission to access content, but display %display has no access control.', array('%display' => $display->display['display_title'])), 'warning');
+          }
+          $filters = $display->getOption('filters');
+          foreach ($filters as $filter) {
+            if ($filter['table'] == 'node' && ($filter['field'] == 'status' || $filter['field'] == 'status_extra')) {
+              continue 2;
+            }
+          }
+          $ret[] = Analyzer::formatMessage(t('Display %display has no access control but does not contain a filter for published nodes.', array('%display' => $display->display['display_title'])), 'warning');
+        }
+      }
+    }
+  }
+  foreach ($view->displayHandlers as $display) {
+    if ($display->getPluginId() == 'page') {
+      if ($display->getOption('path') == 'node/%') {
+        $ret[] = Analyzer::formatMessage(t('Display %display has set node/% as path. This will not produce what you want. If you want to have multiple versions of the node view, use panels.', array('%display' => $display->display['display_title'])), 'warning');
+      }
+    }
+  }
+
+  return $ret;
+}
diff --git a/core/modules/user/user.views.inc b/core/modules/user/user.views.inc
index 0dbe870..fb02878 100644
--- a/core/modules/user/user.views.inc
+++ b/core/modules/user/user.views.inc
@@ -7,6 +7,8 @@
  * @ingroup views_module_handlers
  */
 
+use Drupal\views\ViewExecutable;
+
 /**
  * Implements hook_views_data().
  */
@@ -392,11 +394,3 @@ function user_views_data() {
 
   return $data;
 }
-
-/**
- * Allow replacement of current userid so we can cache these queries
- */
-function user_views_query_substitutions($view) {
-  global $user;
-  return array('***CURRENT_USER***' => intval($user->uid));
-}
diff --git a/core/modules/user/user.views_runtime.inc b/core/modules/user/user.views_runtime.inc
new file mode 100644
index 0000000..f90b7b3
--- /dev/null
+++ b/core/modules/user/user.views_runtime.inc
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * @file
+ * Provide views runtime hooks for user.module.
+ */
+
+use Drupal\views\ViewExecutable;
+
+/**
+ * Allow replacement of current userid so we can cache these queries
+ */
+function user_views_query_substitutions(ViewExecutable $view) {
+  global $user;
+  return array('***CURRENT_USER***' => intval($user->uid));
+}
diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/query/Sql.php b/core/modules/views/lib/Drupal/views/Plugin/views/query/Sql.php
index 55785d2..6288733 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/views/query/Sql.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/query/Sql.php
@@ -1378,7 +1378,7 @@ public function query($get_count = FALSE) {
     }
 
     // Add all query substitutions as metadata.
-    $query->addMetaData('views_substitutions', module_invoke_all('views_query_substitutions', $this));
+    $query->addMetaData('views_substitutions', module_invoke_all('views_query_substitutions', $this->view));
 
     return $query;
   }
diff --git a/core/modules/views/lib/Drupal/views/Tests/ViewsHooksTest.php b/core/modules/views/lib/Drupal/views/Tests/ViewsHooksTest.php
index 745a28c..ad98fa6 100644
--- a/core/modules/views/lib/Drupal/views/Tests/ViewsHooksTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/ViewsHooksTest.php
@@ -16,42 +16,83 @@
 class ViewsHooksTest extends ViewUnitTestBase {
 
   /**
-   * Modules to enable.
+   * Views used by this test.
    *
    * @var array
    */
-  public static $modules = array('views_test_data');
+  public static $testViews = array('test_view');
+
+  /**
+   * An array of available views hooks to test.
+   *
+   * @var array
+   */
+  protected static $hooks = array (
+    'views_data' => 'all',
+    'views_data_alter' => 'alter',
+    'views_query_substitutions' => 'view',
+    'views_form_substitutions' => 'view',
+    'views_analyze' => 'view',
+    'views_pre_view' => 'view',
+    'views_pre_build' => 'view',
+    'views_post_build' => 'view',
+    'views_pre_execute' => 'view',
+    'views_post_execute' => 'view',
+    'views_pre_render' => 'view',
+    'views_post_render' => 'view',
+    'views_query_alter'  => 'view',
+    'views_invalidate_cache' => 'all',
+  );
+
+  /**
+   * The module handler to use for invoking hooks.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
 
   public static function getInfo() {
     return array(
-      'name' => 'Views Hooks',
+      'name' => 'Views hooks',
       'description' => 'Tests that views hooks are registered when defined in $module.views.inc.',
       'group' => 'Views',
     );
   }
 
+  protected function setUp() {
+    parent::setUp();
+
+    $this->moduleHandler = $this->container->get('module_handler');
+  }
+
   /**
    * Tests the hooks.
    */
   public function testHooks() {
-    $hooks = &drupal_static(__FUNCTION__);
-
-    $views_hooks = array(
-      'views_data',
-      'views_data_alter',
-      'views_query_substitutions',
-      'views_form_substitutions',
-      'field_views_data',
-      'field_views_data_alter',
-    );
+    $view = views_get_view('test_view');
+
+    // Test each hook is found in the implementations array and is invoked.
+    foreach (static::$hooks as $hook => $type) {
+      $this->assertTrue($this->moduleHandler->implementsHook('views_test_data', $hook), format_string('The hook @hook was registered.', array('@hook' => $hook)));
+
+      switch ($type) {
+        case 'view':
+          $this->moduleHandler->invoke('views_test_data', $hook, array($view));
+          break;
+
+        case 'alter':
+          $data = array();
+          $this->moduleHandler->invoke('views_test_data', $hook, array($data));
+          break;
 
-    foreach ($views_hooks as $hook) {
-      $implementations = module_implements($hook);
-      $this->assertTrue(in_array('views_test_data', $implementations), format_string('The hook @hook was registered.', array('@hook' => $hook)));
+        default:
+          $this->moduleHandler->invoke('views_test_data', $hook);
+      }
 
-      // Reset the module implements cache, so we ensure that the .views.inc
-      // file is loaded actively.
-      unset($hooks['implementations']);
+      $this->assertTrue($this->container->get('state')->get('views_hook_test_' . $hook), format_string('The %hook hook was invoked.', array('%hook' => $hook)));
+      // Reset the module implementations cache, so we ensure that the
+      // .views.inc file is loaded actively.
+      $this->moduleHandler->resetImplementations();
     }
   }
 
diff --git a/core/modules/views/tests/views_test_data/views_test_data.module b/core/modules/views/tests/views_test_data/views_test_data.module
index 40467c6..a4ae6e4 100644
--- a/core/modules/views/tests/views_test_data/views_test_data.module
+++ b/core/modules/views/tests/views_test_data/views_test_data.module
@@ -63,23 +63,6 @@ function views_test_data_handler_test_access_callback_argument($argument = FALSE
   }
 }
 
-
-/**
- * Implements hook_views_pre_render().
- *
- * @see \Drupal\views\Tests\Plugin\CacheTest
- * @see \Drupal\views\Tests\Plugin\RenderTest
- */
-function views_test_data_views_pre_render(ViewExecutable $view) {
-  if ($view->storage->id() == 'test_cache_header_storage') {
-    $path = drupal_get_path('module', 'views_test_data');
-    $view->element['#attached']['js'][] = "$path/views_cache.test.js";
-    $view->element['#attached']['css'][] = "$path/views_cache.test.css";
-    $view->build_info['pre_render_called'] = TRUE;
-  }
-
-}
-
 /**
  * Implements hook_preprocess_HOOK() for views-view-table.tpl.php.
  */
@@ -92,20 +75,6 @@ function views_test_data_preprocess_views_view_table(&$variables) {
 }
 
 /**
- * Implements hook_views_post_build().
- */
-function views_test_data_views_post_build(ViewExecutable $view) {
-  if ($view->storage->id() == 'test_page_display') {
-    if ($view->current_display == 'page_1') {
-      $view->build_info['denied'] = TRUE;
-    }
-    elseif ($view->current_display == 'page_2') {
-      $view->build_info['fail'] = TRUE;
-    }
-  }
-}
-
-/**
  * Implements hook_preprocess_HOOK() for theme_views_view_mapping_test().
  */
 function template_preprocess_views_view_mapping_test(&$variables) {
diff --git a/core/modules/views/tests/views_test_data/views_test_data.views.inc b/core/modules/views/tests/views_test_data/views_test_data.views.inc
index ff940f7..e6d25c6 100644
--- a/core/modules/views/tests/views_test_data/views_test_data.views.inc
+++ b/core/modules/views/tests/views_test_data/views_test_data.views.inc
@@ -5,60 +5,16 @@
  * Provides views data and hooks for views_test_data module.
  */
 
-use Drupal\views\Analyzer;
-use Drupal\views\ViewExecutable;
-
-/**
- * Implements hook_views_analyze().
- */
-function views_test_data_views_analyze(ViewExecutable $view) {
-  $ret = array();
-
-  $ret[] = Analyzer::formatMessage(t('Test ok message'), 'ok');
-  $ret[] = Analyzer::formatMessage(t('Test warning message'), 'warning');
-  $ret[] = Analyzer::formatMessage(t('Test error message'), 'error');
-
-  return $ret;
-}
-
 /**
  * Implements hook_views_data().
  */
 function views_test_data_views_data() {
-  return state()->get('views_test_data_views_data');
+  state()->set('views_hook_test_views_data', TRUE);
 }
 
 /**
  * Implements hook_views_data_alter().
  */
-function views_test_data_views_data_alter(&$data) {
-
-}
-
-/**
- * Implements hook_views_query_substitutions().
- */
-function views_test_data_views_query_substitutions() {
-
-}
-
-/**
- * Implements hook_views_form_substitutions().
- */
-function views_test_data_views_form_substitutions() {
-
-}
-
-/**
- * Implements hook_field_views_data().
- */
-function views_test_data_field_views_data($field) {
-
-}
-
-/**
- * Implements hook_field_views_data_alter().
- */
-function views_test_data_field_views_data_alter(&$data, $field, $module) {
-
+function views_test_data_views_data_alter() {
+  state()->set('views_hook_test_views_data_alter', TRUE);
 }
diff --git a/core/modules/views/tests/views_test_data/views_test_data.views_runtime.inc b/core/modules/views/tests/views_test_data/views_test_data.views_runtime.inc
new file mode 100644
index 0000000..bb0ad15
--- /dev/null
+++ b/core/modules/views/tests/views_test_data/views_test_data.views_runtime.inc
@@ -0,0 +1,136 @@
+<?php
+
+/**
+ * @file
+ * Provides views data and hooks for views_test_data module.
+ */
+
+use Drupal\views\Analyzer;
+use Drupal\views\ViewExecutable;
+use Drupal\views\ViewStorageInterface;
+
+/**
+ * Implements hook_views_analyze().
+ */
+function views_test_data_views_analyze(ViewExecutable $view) {
+  state()->set('views_hook_test_views_analyze', TRUE);
+
+  $ret = array();
+
+  $ret[] = Analyzer::formatMessage(t('Test ok message'), 'ok');
+  $ret[] = Analyzer::formatMessage(t('Test warning message'), 'warning');
+  $ret[] = Analyzer::formatMessage(t('Test error message'), 'error');
+
+  return $ret;
+}
+
+/**
+ * Implements hook_views_query_substitutions().
+ */
+function views_test_data_views_query_substitutions(ViewExecutable $view) {
+  state()->set('views_hook_test_views_query_substitutions', TRUE);
+}
+
+/**
+ * Implements hook_views_form_substitutions().
+ */
+function views_test_data_views_form_substitutions(ViewExecutable $view) {
+  state()->set('views_hook_test_views_form_substitutions', TRUE);
+}
+
+/**
+ * Implements hook_field_views_data().
+ */
+function views_test_data_field_views_data($field) {
+  state()->set('views_hook_test_field_views_data', TRUE);
+}
+
+/**
+ * Implements hook_field_views_data_alter().
+ */
+function views_test_data_field_views_data_alter(&$data, $field, $module) {
+  state()->set('views_hook_test_field_views_data_alter', TRUE);
+}
+
+/**
+ * Implements hook_views_pre_render().
+ *
+ * @see \Drupal\views\Tests\Plugin\CacheTest
+ * @see \Drupal\views\Tests\Plugin\RenderTest
+ */
+function views_test_data_views_pre_render(ViewExecutable $view) {
+  state()->set('views_hook_test_views_pre_render', TRUE);
+
+  if (isset($view) && ($view->storage->id() == 'test_cache_header_storage')) {
+    $path = drupal_get_path('module', 'views_test_data');
+    $view->element['#attached']['js'][] = "$path/views_cache.test.js";
+    $view->element['#attached']['css'][] = "$path/views_cache.test.css";
+    $view->build_info['pre_render_called'] = TRUE;
+  }
+
+}
+
+/**
+ * Implements hook_views_post_render().
+ */
+function views_test_data_views_post_render(ViewExecutable $view) {
+  state()->set('views_hook_test_views_post_render', TRUE);
+}
+
+/**
+ * Implements hook_views_pre_build().
+ */
+function views_test_data_views_pre_build(ViewExecutable $view) {
+  state()->set('views_hook_test_views_pre_build', TRUE);
+}
+
+/**
+ * Implements hook_views_post_build().
+ */
+function views_test_data_views_post_build(ViewExecutable $view) {
+  state()->set('views_hook_test_views_post_build', TRUE);
+
+  if (isset($view) && ($view->storage->id() == 'test_page_display')) {
+    if ($view->current_display == 'page_1') {
+      $view->build_info['denied'] = TRUE;
+    }
+    elseif ($view->current_display == 'page_2') {
+      $view->build_info['fail'] = TRUE;
+    }
+  }
+}
+
+/**
+ * Implements hook_views_pre_view().
+ */
+function views_test_data_views_pre_view(ViewExecutable $view) {
+  state()->set('views_hook_test_views_pre_view', TRUE);
+}
+
+/**
+ * Implements hook_views_pre_execute().
+ */
+function views_test_data_views_pre_execute(ViewExecutable $view) {
+  state()->set('views_hook_test_views_pre_execute', TRUE);
+}
+
+/**
+ * Implements hook_views_post_execute().
+ */
+function views_test_data_views_post_execute(ViewExecutable $view) {
+  state()->set('views_hook_test_views_post_execute', TRUE);
+}
+
+/**
+ * Implements hook_views_query_alter().
+ */
+function views_test_data_views_query_alter(ViewExecutable $view) {
+  state()->set('views_hook_test_views_query_alter', TRUE);
+}
+
+/**
+ * Implements hook_views_invalidate_cache().
+ */
+function views_test_data_views_invalidate_cache() {
+  state()->set('views_hook_test_views_invalidate_cache', TRUE);
+}
diff --git a/core/modules/views/views.module b/core/modules/views/views.module
index 8dc5f5a..4d9d975 100644
--- a/core/modules/views/views.module
+++ b/core/modules/views/views.module
@@ -821,24 +821,27 @@ function &views_get_current_view() {
  * Implements hook_hook_info().
  */
 function views_hook_info() {
-  $hooks['views_data'] = array(
-    'group' => 'views',
-  );
-  $hooks['views_data_alter'] = array(
-    'group' => 'views',
-  );
-  $hooks['views_query_substitutions'] = array(
-    'group' => 'views',
-  );
-  $hooks['views_form_substitutions'] = array(
-    'group' => 'views',
-  );
-  $hooks['views_analyze'] = array(
-    'group' => 'views',
-  );
-  $hooks['views_data_alter'] = array(
-    'group' => 'views',
-  );
+  $hooks = array();
+
+  $hooks += array_fill_keys(array(
+    'views_data',
+    'views_data_alter',
+  ), array('group' => 'views'));
+
+  $hooks += array_fill_keys(array(
+    'views_query_substitutions',
+    'views_form_substitutions',
+    'views_analyze',
+    'views_pre_view',
+    'views_pre_build',
+    'views_post_build',
+    'views_pre_execute',
+    'views_post_execute',
+    'views_pre_render',
+    'views_post_render',
+    'views_query_alter',
+    'views_invalidate_cache',
+  ), array('group' => 'views_runtime'));
 
   return $hooks;
 }
@@ -1562,18 +1565,6 @@ function views_theme_functions($hook, ViewExecutable $view, $display = NULL) {
 }
 
 /**
- * Substitute current time; this works with cached queries.
- */
-function views_views_query_substitutions($view) {
-  return array(
-    '***CURRENT_VERSION***' => VERSION,
-    '***CURRENT_TIME***' => REQUEST_TIME,
-    '***CURRENT_LANGUAGE***' => language(LANGUAGE_TYPE_CONTENT)->langcode,
-    '***DEFAULT_LANGUAGE***' => language_default()->langcode,
-  );
-}
-
-/**
  * Implements hook_query_TAG_alter().
  *
  * This is the hook_query_alter() for queries tagged by Views and is used to
diff --git a/core/modules/views/views.views_runtime.inc b/core/modules/views/views.views_runtime.inc
new file mode 100644
index 0000000..1b06ebd
--- /dev/null
+++ b/core/modules/views/views.views_runtime.inc
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * @file
+ * Provides views runtime hooks for views.module.
+ */
+
+use Drupal\views\ViewExecutable;
+
+/**
+ * Implements hook_views_query_substitutions().
+ *
+ * Substitute current time; this works with cached queries.
+ */
+function views_views_query_substitutions(ViewExecutable $view) {
+  return array(
+    '***CURRENT_VERSION***' => VERSION,
+    '***CURRENT_TIME***' => REQUEST_TIME,
+    '***CURRENT_LANGUAGE***' => language(LANGUAGE_TYPE_CONTENT)->langcode,
+    '***DEFAULT_LANGUAGE***' => language_default()->langcode,
+  );
+}
