Index: src/wp-includes/class-wp-hook.php
===================================================================
--- src/wp-includes/class-wp-hook.php	(revision 0)
+++ src/wp-includes/class-wp-hook.php	(working copy)
@@ -0,0 +1,522 @@
+<?php
+/**
+ * Plugin API: WP_Hook class
+ *
+ * @package WordPress
+ * @subpackage Plugin
+ * @since 4.7.0
+ */
+
+/**
+ * Core class used to implement action and filter hook functionality.
+ *
+ * @since 4.7.0
+ *
+ * @see Iterator
+ * @see ArrayAccess
+ */
+final class WP_Hook implements Iterator, ArrayAccess {
+
+	/**
+	 * Hook callbacks.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 * @var array
+	 */
+	public $callbacks = array();
+
+	/**
+	 * The priority keys of actively running iterations of a hook.
+	 *
+	 * @since 4.7.0
+	 * @access private
+	 * @var array
+	 */
+	private $iterations = array();
+
+	/**
+	 * The current priority of actively running iterations of a hook.
+	 *
+	 * @since 4.7.0
+	 * @access private
+	 * @var array
+	 */
+	private $current_priority = array();
+
+	/**
+	 * Number of levels this hook can be recursively called.
+	 *
+	 * @since 4.7.0
+	 * @access private
+	 * @var int
+	 */
+	private $nesting_level = 0;
+
+	/**
+	 * Hooks a function or method to a specific filter action.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @param string   $tag             The name of the filter to hook the $function_to_add callback to.
+	 * @param callable $function_to_add The callback to be run when the filter is applied.
+	 * @param int      $priority        The order in which the functions associated with a
+	 *                                  particular action are executed. Lower numbers correspond with
+	 *                                  earlier execution, and functions with the same priority are executed
+	 *                                  in the order in which they were added to the action.
+	 * @param int      $accepted_args   The number of arguments the function accepts.
+	 */
+	public function add_filter( $tag, $function_to_add, $priority, $accepted_args ) {
+		$idx = _wp_filter_build_unique_id( $tag, $function_to_add, $priority );
+		$priority_existed = isset( $this->callbacks[ $priority ] );
+
+		$this->callbacks[ $priority ][ $idx ] = array(
+			'function' => $function_to_add,
+			'accepted_args' => $accepted_args
+		);
+
+		// if we're adding a new priority to the list, put them back in sorted order
+		if ( ! $priority_existed && count( $this->callbacks ) > 1 ) {
+			ksort( $this->callbacks, SORT_NUMERIC );
+		}
+
+		if ( $this->nesting_level > 0 ) {
+			$this->resort_active_iterations( $priority, $priority_existed );
+		}
+	}
+
+	/**
+	 * Handles reseting callback priority keys mid-iteration.
+	 *
+	 * @since 4.7.0
+	 * @access private
+	 *
+	 * @param bool|int $new_priority      The priority of the new filter being added. Default false, for no priority being added.
+	 * @param bool     $priority_existed  Flag for whether the priority already existed before the new filter was added.
+	 */
+	private function resort_active_iterations( $new_priority = false, $priority_existed = false ) {
+		$new_priorities = array_keys( $this->callbacks );
+
+		// If there are no remaining hooks, clear out all running iterations.
+		if ( ! $new_priorities ) {
+			foreach ( $this->iterations as $index => $iteration ) {
+				$this->iterations[ $index ] = $new_priorities;
+			}
+			return;
+		}
+
+		$min = min( $new_priorities );
+		foreach ( $this->iterations as $index => &$iteration ) {
+			$current = current( $iteration );
+			// If we're already at the end of this iteration, just leave the array pointer where it is.
+			if ( false === $current ) {
+				continue;
+			}
+
+			$iteration = $new_priorities;
+
+			if ( $current < $min ) {
+				array_unshift( $iteration, $current );
+				continue;
+			}
+
+			while ( current( $iteration ) < $current ) {
+				if ( false === next( $iteration ) ) {
+					break;
+				}
+			}
+
+			// If we have a new priority that didn't exist, but ::apply_filters() or ::do_action() thinks it's the current priority...
+			if ( $new_priority === $this->current_priority[ $index ] && ! $priority_existed ) {
+				// ... and the new priority is the same as what $this->iterations thinks is the previous priority,
+				// We need to move back to it.
+
+				if ( false === current( $iteration ) ) {
+					// If we've already moved off the end of the array, go back to the last element.
+					$prev = end( $iteration );
+				} else {
+					// Otherwise, just go back to the previous element.
+					$prev = prev( $iteration );
+				}
+				if ( false === $prev ) {
+					// Start of the array. Reset, and go about our day.
+					reset( $iteration );
+				} elseif ( $new_priority !== $prev ) {
+					// Previous wasn't the same. Move forward again.
+					next( $iteration );
+				}
+			}
+		}
+		unset( $iteration );
+	}
+
+	/**
+	 * Unhooks a function or method from a specific filter action.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @param string   $tag                The filter hook to which the function to be removed is hooked. Used
+	 *                                     for building the callback ID when SPL is not available.
+	 * @param callable $function_to_remove The callback to be removed from running when the filter is applied.
+	 * @param int      $priority           The exact priority used when adding the original filter callback.
+	 * @return bool Whether the callback existed before it was removed.
+	 */
+	public function remove_filter( $tag, $function_to_remove, $priority ) {
+		$function_key = _wp_filter_build_unique_id( $tag, $function_to_remove, $priority );
+
+		$exists = isset( $this->callbacks[ $priority ][ $function_key ] );
+		if ( $exists ) {
+			unset( $this->callbacks[ $priority ][ $function_key ] );
+			if ( ! $this->callbacks[ $priority ] ) {
+				unset( $this->callbacks[ $priority ] );
+				if ( $this->nesting_level > 0 ) {
+					$this->resort_active_iterations();
+				}
+			}
+		}
+		return $exists;
+	}
+
+	/**
+	 * Checks if a specific action has been registered for this hook.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @param callable|bool $function_to_check Optional. The callback to check for. Default false.
+	 * @param string        $tag               Optional. The name of the filter hook. Default empty.
+	 *                                         Used for building the callback ID when SPL is not available.
+	 * @return bool|int The priority of that hook is returned, or false if the function is not attached.
+	 */
+	public function has_filter( $tag = '', $function_to_check = false ) {
+		if ( false === $function_to_check ) {
+			return $this->has_filters();
+		}
+
+		$function_key = _wp_filter_build_unique_id( $tag, $function_to_check, false );
+		if ( ! $function_key ) {
+			return false;
+		}
+
+		foreach ( $this->callbacks as $priority => $callbacks ) {
+			if ( isset( $callbacks[ $function_key ] ) ) {
+				return $priority;
+			}
+		}
+
+		return false;
+	}
+
+	/**
+	 * Checks if any callbacks have been registered for this hook.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @return bool True if callbacks have been registered for the current hook, false otherwise.
+	 */
+	public function has_filters() {
+		foreach ( $this->callbacks as $callbacks ) {
+			if ( $callbacks ) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Removes all callbacks from the current filter.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @param int|bool $priority Optional. The priority number to remove. Default false.
+	 */
+	public function remove_all_filters( $priority = false ) {
+		if ( ! $this->callbacks ) {
+			return;
+		}
+
+		if ( false === $priority ) {
+			$this->callbacks = array();
+		} else if ( isset( $this->callbacks[ $priority ] ) ) {
+			unset( $this->callbacks[ $priority ] );
+		}
+
+		if ( $this->nesting_level > 0 ) {
+			$this->resort_active_iterations();
+		}
+	}
+
+	/**
+	 * Calls the callback functions added to a filter hook.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @param mixed $value The value to filter.
+	 * @param array $args  Arguments to pass to callbacks.
+	 * @return mixed The filtered value after all hooked functions are applied to it.
+	 */
+	public function apply_filters( $value, $args ) {
+		if ( ! $this->callbacks ) {
+			return $value;
+		}
+		$nesting_level = $this->nesting_level++;
+
+		$this->iterations[ $nesting_level ] = array_keys( $this->callbacks );
+		$num_args = count( $args );
+
+		do {
+			$this->current_priority[ $nesting_level ] = $priority = current( $this->iterations[ $nesting_level ] );
+
+			foreach ( $this->callbacks[ $priority ] as $the_ ) {
+				$args[ 0 ] = $value;
+
+				// Avoid the array_slice if possible.
+				if ( $the_['accepted_args'] == 0 ) {
+					$value = call_user_func_array( $the_['function'], array() );
+				} elseif ( $the_['accepted_args'] >= $num_args ) {
+					$value = call_user_func_array( $the_['function'], $args );
+				} else {
+					$value = call_user_func_array( $the_['function'], array_slice( $args, 0, (int)$the_['accepted_args'] ) );
+				}
+			}
+		} while ( false !== next( $this->iterations[ $nesting_level ] ) );
+
+		unset( $this->iterations[ $nesting_level ] );
+		unset( $this->current_priority[ $nesting_level ] );
+
+		$this->nesting_level--;
+
+		return $value;
+	}
+
+	/**
+	 * Executes the callback functions hooked on a specific action hook.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @param mixed $args Arguments to pass to the hook callbacks.
+	 */
+	public function do_action( $args ) {
+		if ( ! $this->callbacks ) {
+			return;
+		}
+		$nesting_level = $this->nesting_level++;
+		$this->iterations[ $nesting_level ] = array_keys( $this->callbacks );
+		$num_args = count( $args );
+
+		do {
+			$this->current_priority[ $nesting_level ] = $priority = current( $this->iterations[ $nesting_level ] );
+
+			foreach ( $this->callbacks[ $priority ] as $the_ ) {
+
+				// Avoid the array_slice if possible.
+				if ( $the_['accepted_args'] == 0 ) {
+					call_user_func_array( $the_['function'], array() );
+				} elseif ( $the_['accepted_args'] >= $num_args ) {
+					call_user_func_array( $the_['function'], $args );
+				} else {
+					call_user_func_array( $the_['function'], array_slice( $args, 0, (int)$the_['accepted_args'] ) );
+				}
+			}
+		} while ( next( $this->iterations[ $nesting_level ] ) !== false );
+
+		unset( $this->iterations[ $nesting_level ] );
+		unset( $this->current_priority[ $nesting_level ] );
+
+		$this->nesting_level--;
+	}
+
+	/**
+	 * Processes the functions hooked into the 'all' hook.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @param array $args Arguments to pass to the hook callbacks. Passed by reference.
+	 */
+	public function do_all_hook( &$args ) {
+		$nesting_level = $this->nesting_level++;
+		$this->iterations[ $nesting_level ] = array_keys( $this->callbacks );
+
+		do {
+			$priority = current( $this->iterations[ $nesting_level ] );
+			foreach ( $this->callbacks[ $priority ] as $the_ ) {
+				call_user_func_array( $the_['function'], $args );
+			}
+		} while ( false !== next( $this->iterations[ $nesting_level ] ) );
+
+		unset( $this->iterations[ $nesting_level ] );
+		$this->nesting_level--;
+	}
+
+	/**
+	 * Normalizes filters setup before WordPress has initialized to WP_Hook objects.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 * @static
+	 *
+	 * @param array $filters Filters to normalize.
+	 * @return WP_Hook[] Array of normalized filters.
+	 */
+	public static function build_preinitialized_hooks( $filters ) {
+		/** @var WP_Hook[] $normalized */
+		$normalized = array();
+
+		foreach ( $filters as $tag => $callback_groups ) {
+			if ( is_object( $callback_groups ) && $callback_groups instanceof WP_Hook ) {
+				$normalized[ $tag ] = $callback_groups;
+				continue;
+			}
+			$hook = new WP_Hook();
+
+			// Loop through callback groups.
+			foreach ( $callback_groups as $priority => $callbacks ) {
+
+				// Loop through callbacks.
+				foreach ( $callbacks as $cb ) {
+					$hook->add_filter( $tag, $cb['function'], $priority, $cb['accepted_args'] );
+				}
+			}
+			$normalized[ $tag ] = $hook;
+		}
+		return $normalized;
+	}
+
+	/**
+	 * Determines whether an offset value exists.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @link http://php.net/manual/en/arrayaccess.offsetexists.php
+	 *
+	 * @param mixed $offset An offset to check for.
+	 * @return bool True if the offset exists, false otherwise.
+	 */
+	public function offsetExists( $offset ) {
+		return isset( $this->callbacks[ $offset ] );
+	}
+
+	/**
+	 * Retrieves a value at a specified offset.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @link http://php.net/manual/en/arrayaccess.offsetget.php
+	 *
+	 * @param mixed $offset The offset to retrieve.
+	 * @return mixed If set, the value at the specified offset, null otherwise.
+	 */
+	public function offsetGet( $offset ) {
+		return isset( $this->callbacks[ $offset ] ) ? $this->callbacks[ $offset ] : null;
+	}
+
+	/**
+	 * Sets a value at a specified offset.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @link http://php.net/manual/en/arrayaccess.offsetset.php
+	 *
+	 * @param mixed $offset The offset to assign the value to.
+	 * @param mixed $value The value to set.
+	 */
+	public function offsetSet( $offset, $value ) {
+		if ( is_null( $offset ) ) {
+			$this->callbacks[] = $value;
+		} else {
+			$this->callbacks[ $offset ] = $value;
+		}
+	}
+
+	/**
+	 * Unsets a specified offset.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @link http://php.net/manual/en/arrayaccess.offsetunset.php
+	 *
+	 * @param mixed $offset The offset to unset.
+	 */
+	public function offsetUnset( $offset ) {
+		unset( $this->callbacks[ $offset ] );
+	}
+
+	/**
+	 * Return the current element
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @link http://php.net/manual/en/iterator.current.php
+	 *
+	 * @return mixed
+	 */
+	public function current() {
+		return current( $this->callbacks );
+	}
+
+	/**
+	 * Move forward to the next element
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @link http://php.net/manual/en/iterator.next.php
+	 */
+	public function next() {
+		return next( $this->callbacks );
+	}
+
+	/**
+	 * Return the key of the current element
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @link http://php.net/manual/en/iterator.key.php
+	 *
+	 * @return mixed Returns scalar on success, or NULL on failure
+	 */
+	public function key() {
+		return key( $this->callbacks );
+	}
+
+	/**
+	 * Checks if current position is valid
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @link http://php.net/manual/en/iterator.valid.php
+	 *
+	 * @return boolean
+	 */
+	public function valid() {
+		return key( $this->callbacks ) !== null;
+	}
+
+	/**
+	 * Rewind the Iterator to the first element
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @link http://php.net/manual/en/iterator.rewind.php
+	 */
+	public function rewind() {
+		return reset( $this->callbacks );
+	}
+
+
+}

Property changes on: src/wp-includes/class-wp-hook.php
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: src/wp-includes/plugin.php
===================================================================
--- src/wp-includes/plugin.php	(revision 38516)
+++ src/wp-includes/plugin.php	(working copy)
@@ -22,17 +22,20 @@
  */
 
 // Initialize the filter globals.
-global $wp_filter, $wp_actions, $merged_filters, $wp_current_filter;
+require( ABSPATH . WPINC . '/class-wp-hook.php' );
 
-if ( ! isset( $wp_filter ) )
+/** @var WP_Hook[] $wp_filter */
+global $wp_filter, $wp_actions, $wp_current_filter;
+
+if ( $wp_filter ) {
+	$wp_filter = WP_Hook::build_preinitialized_hooks( $wp_filter );
+} else {
 	$wp_filter = array();
+}
 
 if ( ! isset( $wp_actions ) )
 	$wp_actions = array();
 
-if ( ! isset( $merged_filters ) )
-	$merged_filters = array();
-
 if ( ! isset( $wp_current_filter ) )
 	$wp_current_filter = array();
 
@@ -89,8 +92,6 @@
  * @since 0.71
  *
  * @global array $wp_filter      A multidimensional array of all hooks and the callbacks hooked to them.
- * @global array $merged_filters Tracks the tags that need to be merged for later. If the hook is added,
- *                               it doesn't need to run through that process.
  *
  * @param string   $tag             The name of the filter to hook the $function_to_add callback to.
  * @param callable $function_to_add The callback to be run when the filter is applied.
@@ -103,11 +104,11 @@
  * @return true
  */
 function add_filter( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
-	global $wp_filter, $merged_filters;
-
-	$idx = _wp_filter_build_unique_id($tag, $function_to_add, $priority);
-	$wp_filter[$tag][$priority][$idx] = array('function' => $function_to_add, 'accepted_args' => $accepted_args);
-	unset( $merged_filters[ $tag ] );
+	global $wp_filter;
+	if ( ! isset( $wp_filter[ $tag ] ) ) {
+		$wp_filter[ $tag ] = new WP_Hook();
+	}
+	$wp_filter[ $tag ]->add_filter( $tag, $function_to_add, $priority, $accepted_args );
 	return true;
 }
 
@@ -128,38 +129,13 @@
  *                   return value.
  */
 function has_filter($tag, $function_to_check = false) {
-	// Don't reset the internal array pointer
-	$wp_filter = $GLOBALS['wp_filter'];
-
-	$has = ! empty( $wp_filter[ $tag ] );
-
-	// Make sure at least one priority has a filter callback
-	if ( $has ) {
-		$exists = false;
-		foreach ( $wp_filter[ $tag ] as $callbacks ) {
-			if ( ! empty( $callbacks ) ) {
-				$exists = true;
-				break;
-			}
-		}
-
-		if ( ! $exists ) {
-			$has = false;
-		}
-	}
-
-	if ( false === $function_to_check || false === $has )
-		return $has;
+	global $wp_filter;
 
-	if ( !$idx = _wp_filter_build_unique_id($tag, $function_to_check, false) )
+	if ( ! isset( $wp_filter[ $tag ] ) ) {
 		return false;
-
-	foreach ( (array) array_keys($wp_filter[$tag]) as $priority ) {
-		if ( isset($wp_filter[$tag][$priority][$idx]) )
-			return $priority;
 	}
 
-	return false;
+	return $wp_filter[ $tag ]->has_filter( $tag, $function_to_check );
 }
 
 /**
@@ -190,7 +166,6 @@
  * @since 0.71
  *
  * @global array $wp_filter         Stores all of the filters.
- * @global array $merged_filters    Merges the filter hooks using this function.
  * @global array $wp_current_filter Stores the list of current filters with the current one last.
  *
  * @param string $tag     The name of the filter hook.
@@ -199,7 +174,7 @@
  * @return mixed The filtered value after all hooked functions are applied to it.
  */
 function apply_filters( $tag, $value ) {
-	global $wp_filter, $merged_filters, $wp_current_filter;
+	global $wp_filter, $wp_current_filter;
 
 	$args = array();
 
@@ -219,29 +194,17 @@
 	if ( !isset($wp_filter['all']) )
 		$wp_current_filter[] = $tag;
 
-	// Sort.
-	if ( !isset( $merged_filters[ $tag ] ) ) {
-		ksort($wp_filter[$tag]);
-		$merged_filters[ $tag ] = true;
-	}
-
-	reset( $wp_filter[ $tag ] );
-
 	if ( empty($args) )
 		$args = func_get_args();
 
-	do {
-		foreach ( (array) current($wp_filter[$tag]) as $the_ )
-			if ( !is_null($the_['function']) ){
-				$args[1] = $value;
-				$value = call_user_func_array($the_['function'], array_slice($args, 1, (int) $the_['accepted_args']));
-			}
+	// don't pass the tag name to WP_Hook
+	array_shift( $args );
 
-	} while ( next($wp_filter[$tag]) !== false );
+	$filtered = $wp_filter[ $tag ]->apply_filters( $value, $args );
 
 	array_pop( $wp_current_filter );
 
-	return $value;
+	return $filtered;
 }
 
 /**
@@ -253,7 +216,6 @@
  * functions hooked to `$tag` are supplied using an array.
  *
  * @global array $wp_filter         Stores all of the filters
- * @global array $merged_filters    Merges the filter hooks using this function.
  * @global array $wp_current_filter Stores the list of current filters with the current one last
  *
  * @param string $tag  The name of the filter hook.
@@ -261,7 +223,7 @@
  * @return mixed The filtered value after all hooked functions are applied to it.
  */
 function apply_filters_ref_array($tag, $args) {
-	global $wp_filter, $merged_filters, $wp_current_filter;
+	global $wp_filter, $wp_current_filter;
 
 	// Do 'all' actions first
 	if ( isset($wp_filter['all']) ) {
@@ -279,24 +241,11 @@
 	if ( !isset($wp_filter['all']) )
 		$wp_current_filter[] = $tag;
 
-	// Sort
-	if ( !isset( $merged_filters[ $tag ] ) ) {
-		ksort($wp_filter[$tag]);
-		$merged_filters[ $tag ] = true;
-	}
-
-	reset( $wp_filter[ $tag ] );
-
-	do {
-		foreach ( (array) current($wp_filter[$tag]) as $the_ )
-			if ( !is_null($the_['function']) )
-				$args[0] = call_user_func_array($the_['function'], array_slice($args, 0, (int) $the_['accepted_args']));
-
-	} while ( next($wp_filter[$tag]) !== false );
+	$filtered = $wp_filter[ $tag ]->apply_filters( $args[0], $args );
 
 	array_pop( $wp_current_filter );
 
-	return $args[0];
+	return $filtered;
 }
 
 /**
@@ -313,7 +262,6 @@
  * @since 1.2.0
  *
  * @global array $wp_filter         Stores all of the filters
- * @global array $merged_filters    Merges the filter hooks using this function.
  *
  * @param string   $tag                The filter hook to which the function to be removed is hooked.
  * @param callable $function_to_remove The name of the function which should be removed.
@@ -321,19 +269,14 @@
  * @return bool    Whether the function existed before it was removed.
  */
 function remove_filter( $tag, $function_to_remove, $priority = 10 ) {
-	$function_to_remove = _wp_filter_build_unique_id( $tag, $function_to_remove, $priority );
-
-	$r = isset( $GLOBALS['wp_filter'][ $tag ][ $priority ][ $function_to_remove ] );
+	global $wp_filter;
 
-	if ( true === $r ) {
-		unset( $GLOBALS['wp_filter'][ $tag ][ $priority ][ $function_to_remove ] );
-		if ( empty( $GLOBALS['wp_filter'][ $tag ][ $priority ] ) ) {
-			unset( $GLOBALS['wp_filter'][ $tag ][ $priority ] );
+	$r = false;
+	if ( isset( $wp_filter[ $tag ] ) ) {
+		$r = $wp_filter[ $tag ]->remove_filter( $tag, $function_to_remove, $priority );
+		if ( ! $wp_filter[ $tag ]->callbacks ) {
+			unset( $wp_filter[ $tag ] );
 		}
-		if ( empty( $GLOBALS['wp_filter'][ $tag ] ) ) {
-			$GLOBALS['wp_filter'][ $tag ] = array();
-		}
-		unset( $GLOBALS['merged_filters'][ $tag ] );
 	}
 
 	return $r;
@@ -344,26 +287,22 @@
  *
  * @since 2.7.0
  *
- * @global array $wp_filter         Stores all of the filters
- * @global array $merged_filters    Merges the filter hooks using this function.
+ * @global array $wp_filter  Stores all of the filters
  *
  * @param string   $tag      The filter to remove hooks from.
  * @param int|bool $priority Optional. The priority number to remove. Default false.
  * @return true True when finished.
  */
 function remove_all_filters( $tag, $priority = false ) {
-	global $wp_filter, $merged_filters;
+	global $wp_filter;
 
 	if ( isset( $wp_filter[ $tag ]) ) {
-		if ( false === $priority ) {
-			$wp_filter[ $tag ] = array();
-		} elseif ( isset( $wp_filter[ $tag ][ $priority ] ) ) {
-			$wp_filter[ $tag ][ $priority ] = array();
+		$wp_filter[ $tag ]->remove_all_filters( $priority );
+		if ( ! $wp_filter[ $tag ]->has_filters() ) {
+			unset( $wp_filter[ $tag ] );
 		}
 	}
 
-	unset( $merged_filters[ $tag ] );
-
 	return true;
 }
 
@@ -473,7 +412,6 @@
  *
  * @global array $wp_filter         Stores all of the filters
  * @global array $wp_actions        Increments the amount of times action was triggered.
- * @global array $merged_filters    Merges the filter hooks using this function.
  * @global array $wp_current_filter Stores the list of current filters with the current one last
  *
  * @param string $tag     The name of the action to be executed.
@@ -481,7 +419,7 @@
  *                        functions hooked to the action. Default empty.
  */
 function do_action($tag, $arg = '') {
-	global $wp_filter, $wp_actions, $merged_filters, $wp_current_filter;
+	global $wp_filter, $wp_actions, $wp_current_filter;
 
 	if ( ! isset($wp_actions[$tag]) )
 		$wp_actions[$tag] = 1;
@@ -512,20 +450,7 @@
 	for ( $a = 2, $num = func_num_args(); $a < $num; $a++ )
 		$args[] = func_get_arg($a);
 
-	// Sort
-	if ( !isset( $merged_filters[ $tag ] ) ) {
-		ksort($wp_filter[$tag]);
-		$merged_filters[ $tag ] = true;
-	}
-
-	reset( $wp_filter[ $tag ] );
-
-	do {
-		foreach ( (array) current($wp_filter[$tag]) as $the_ )
-			if ( !is_null($the_['function']) )
-				call_user_func_array($the_['function'], array_slice($args, 0, (int) $the_['accepted_args']));
-
-	} while ( next($wp_filter[$tag]) !== false );
+	$wp_filter[ $tag ]->do_action( $args );
 
 	array_pop($wp_current_filter);
 }
@@ -558,14 +483,13 @@
  *                  functions hooked to $tag< are supplied using an array.
  * @global array $wp_filter         Stores all of the filters
  * @global array $wp_actions        Increments the amount of times action was triggered.
- * @global array $merged_filters    Merges the filter hooks using this function.
  * @global array $wp_current_filter Stores the list of current filters with the current one last
  *
  * @param string $tag  The name of the action to be executed.
  * @param array  $args The arguments supplied to the functions hooked to `$tag`.
  */
 function do_action_ref_array($tag, $args) {
-	global $wp_filter, $wp_actions, $merged_filters, $wp_current_filter;
+	global $wp_filter, $wp_actions, $wp_current_filter;
 
 	if ( ! isset($wp_actions[$tag]) )
 		$wp_actions[$tag] = 1;
@@ -588,20 +512,7 @@
 	if ( !isset($wp_filter['all']) )
 		$wp_current_filter[] = $tag;
 
-	// Sort
-	if ( !isset( $merged_filters[ $tag ] ) ) {
-		ksort($wp_filter[$tag]);
-		$merged_filters[ $tag ] = true;
-	}
-
-	reset( $wp_filter[ $tag ] );
-
-	do {
-		foreach ( (array) current($wp_filter[$tag]) as $the_ )
-			if ( !is_null($the_['function']) )
-				call_user_func_array($the_['function'], array_slice($args, 0, (int) $the_['accepted_args']));
-
-	} while ( next($wp_filter[$tag]) !== false );
+	$wp_filter[ $tag ]->do_action( $args );
 
 	array_pop($wp_current_filter);
 }
@@ -923,13 +834,7 @@
 function _wp_call_all_hook($args) {
 	global $wp_filter;
 
-	reset( $wp_filter['all'] );
-	do {
-		foreach ( (array) current($wp_filter['all']) as $the_ )
-			if ( !is_null($the_['function']) )
-				call_user_func_array($the_['function'], $args);
-
-	} while ( next($wp_filter['all']) !== false );
+	$wp_filter['all']->do_all_hook( $args );
 }
 
 /**
Index: tests/phpunit/includes/functions.php
===================================================================
--- tests/phpunit/includes/functions.php	(revision 38516)
+++ tests/phpunit/includes/functions.php	(working copy)
@@ -21,18 +21,14 @@
 
 // For adding hooks before loading WP
 function tests_add_filter($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
-	global $wp_filter, $merged_filters;
+	global $wp_filter;
 
 	$idx = _test_filter_build_unique_id($tag, $function_to_add, $priority);
 	$wp_filter[$tag][$priority][$idx] = array('function' => $function_to_add, 'accepted_args' => $accepted_args);
-	unset( $merged_filters[ $tag ] );
 	return true;
 }
 
 function _test_filter_build_unique_id($tag, $function, $priority) {
-	global $wp_filter;
-	static $filter_id_count = 0;
-
 	if ( is_string($function) )
 		return $function;
 
Index: tests/phpunit/includes/testcase.php
===================================================================
--- tests/phpunit/includes/testcase.php	(revision 38516)
+++ tests/phpunit/includes/testcase.php	(working copy)
@@ -223,10 +223,14 @@
 	 * @return void
 	 */
 	protected function _backup_hooks() {
-		$globals = array( 'merged_filters', 'wp_actions', 'wp_current_filter', 'wp_filter' );
+		$globals = array( 'wp_actions', 'wp_current_filter' );
 		foreach ( $globals as $key ) {
 			self::$hooks_saved[ $key ] = $GLOBALS[ $key ];
 		}
+		self::$hooks_saved['wp_filter'] = array();
+		foreach ( $GLOBALS['wp_filter'] as $hook_name => $hook_object ) {
+			self::$hooks_saved['wp_filter'][ $hook_name ] = clone $hook_object;
+		}
 	}
 
 	/**
@@ -240,12 +244,18 @@
 	 * @return void
 	 */
 	protected function _restore_hooks() {
-		$globals = array( 'merged_filters', 'wp_actions', 'wp_current_filter', 'wp_filter' );
+		$globals = array( 'wp_actions', 'wp_current_filter' );
 		foreach ( $globals as $key ) {
 			if ( isset( self::$hooks_saved[ $key ] ) ) {
 				$GLOBALS[ $key ] = self::$hooks_saved[ $key ];
 			}
 		}
+		if ( isset( self::$hooks_saved['wp_filter'] ) ) {
+			$GLOBALS['wp_filter'] = array();
+			foreach ( self::$hooks_saved['wp_filter'] as $hook_name => $hook_object ) {
+				$GLOBALS['wp_filter'][ $hook_name ] = clone $hook_object;
+			}
+		}
 	}
 
 	static function flush_cache() {
Index: tests/phpunit/tests/actions.php
===================================================================
--- tests/phpunit/tests/actions.php	(revision 38516)
+++ tests/phpunit/tests/actions.php	(working copy)
@@ -114,6 +114,45 @@
 		$this->assertEquals( array( $val1 ), array_pop( $argsvar2 ) );
 	}
 
+	/**
+	 * Test that multiple callbacks receive the correct number of args even when the number
+	 * is less than, or greater than previous hooks.
+	 *
+	 * @see https://core.trac.wordpress.org/ticket/17817#comment:72
+	 * @ticket 17817
+	 */
+	function test_action_args_3() {
+		$a1 = new MockAction();
+		$a2 = new MockAction();
+		$a3 = new MockAction();
+		$tag = rand_str();
+		$val1 = rand_str();
+		$val2 = rand_str();
+
+		// a1 accepts two arguments, a2 doesn't, a3 accepts two arguments
+		add_action( $tag, array( &$a1, 'action' ), 10, 2 );
+		add_action( $tag, array( &$a2, 'action' ) );
+		add_action( $tag, array( &$a3, 'action' ), 10, 2 );
+		// call the action with two arguments
+		do_action( $tag, $val1, $val2 );
+
+		$call_count = $a1->get_call_count();
+		// a1 should be called with both args
+		$this->assertEquals( 1, $call_count );
+		$argsvar1 = $a1->get_args();
+		$this->assertEquals( array( $val1, $val2 ), array_pop( $argsvar1 ) );
+
+		// a2 should be called with one only
+		$this->assertEquals( 1, $a2->get_call_count() );
+		$argsvar2 = $a2->get_args();
+		$this->assertEquals( array( $val1 ), array_pop( $argsvar2 ) );
+
+		// a3 should be called with both args
+		$this->assertEquals( 1, $a3->get_call_count() );
+		$argsvar3 = $a3->get_args();
+		$this->assertEquals( array( $val1, $val2 ), array_pop( $argsvar3 ) );
+	}
+
 	function test_action_priority() {
 		$a = new MockAction();
 		$tag = rand_str();
@@ -258,6 +297,120 @@
 	}
 
 	/**
+	 * @ticket 17817
+	 */
+	function test_action_recursion() {
+		$tag = rand_str();
+		$a = new MockAction();
+		$b = new MockAction();
+
+		add_action( $tag, array( $a, 'action' ), 11, 1 );
+		add_action( $tag, array( $b, 'action' ), 13, 1 );
+		add_action( $tag, array( $this, 'action_that_causes_recursion' ), 12, 1 );
+		do_action( $tag, $tag );
+
+		$this->assertEquals( 2, $a->get_call_count(), 'recursive actions should call all callbacks with earlier priority' );
+		$this->assertEquals( 2, $b->get_call_count(), 'recursive actions should call callbacks with later priority' );
+	}
+
+	function action_that_causes_recursion( $tag ) {
+		static $recursing = false;
+		if ( ! $recursing ) {
+			$recursing = true;
+			do_action( $tag, $tag );
+		}
+		$recursing = false;
+	}
+
+	/**
+	 * @ticket 9968
+	 * @ticket 17817
+	 */
+	function test_action_callback_manipulation_while_running() {
+		$tag = rand_str();
+		$a = new MockAction();
+		$b = new MockAction();
+		$c = new MockAction();
+		$d = new MockAction();
+		$e = new MockAction();
+
+		add_action( $tag, array( $a, 'action' ), 11, 2 );
+		add_action( $tag, array( $this, 'action_that_manipulates_a_running_hook' ), 12, 2 );
+		add_action( $tag, array( $b, 'action' ), 12, 2 );
+
+		do_action( $tag, $tag, array( $a, $b, $c, $d, $e ) );
+		do_action( $tag, $tag, array( $a, $b, $c, $d, $e ) );
+
+		$this->assertEquals( 2, $a->get_call_count(), 'callbacks should run unless otherwise instructed' );
+		$this->assertEquals( 1, $b->get_call_count(), 'callback removed by same priority callback should still get called' );
+		$this->assertEquals( 1, $c->get_call_count(), 'callback added by same priority callback should not get called' );
+		$this->assertEquals( 2, $d->get_call_count(), 'callback added by earlier priority callback should get called' );
+		$this->assertEquals( 1, $e->get_call_count(), 'callback added by later priority callback should not get called' );
+	}
+
+	function action_that_manipulates_a_running_hook( $tag, $mocks ) {
+		remove_action( $tag, array( $mocks[ 1 ], 'action' ), 12, 2 );
+		add_action( $tag, array( $mocks[ 2 ], 'action' ), 12, 2 );
+		add_action( $tag, array( $mocks[ 3 ], 'action' ), 13, 2 );
+		add_action( $tag, array( $mocks[ 4 ], 'action' ), 10, 2 );
+	}
+
+	/**
+	 * @ticket 17817
+	 *
+	 * This specificaly addresses the concern raised at
+	 * https://core.trac.wordpress.org/ticket/17817#comment:52
+	 */
+	function test_remove_anonymous_callback() {
+		$tag = rand_str();
+		$a = new MockAction();
+		add_action( $tag, array( $a, 'action' ), 12, 1 );
+		$this->assertTrue( has_action( $tag ) );
+
+		$hook = $GLOBALS['wp_filter'][ $tag ];
+
+		// From http://wordpress.stackexchange.com/a/57088/6445
+		foreach ( $hook as $priority => $filter ) {
+			foreach ( $filter as $identifier => $function ) {
+				if ( is_array( $function )
+					&& is_a( $function['function'][ 0 ], 'MockAction' )
+					&& 'action' === $function['function'][ 1 ]
+				) {
+					remove_filter(
+						$tag,
+						array( $function['function'][ 0 ], 'action' ),
+						$priority
+					);
+				}
+			}
+		}
+
+		$this->assertFalse( has_action( $tag ) );
+	}
+
+
+	/**
+	 * Test the ArrayAccess methods of WP_Hook
+	 *
+	 * @ticket 17817
+	 */
+	function test_array_access_of_wp_filter_global() {
+		global $wp_filter;
+		$tag = rand_str();
+
+		add_action( $tag, '__return_null', 11, 1 );
+
+		$this->assertTrue( isset( $wp_filter[ $tag ][ 11 ] ) );
+		$this->assertArrayHasKey( '__return_null', $wp_filter[ $tag ][ 11 ] );
+
+		unset( $wp_filter[ $tag ][ 11 ] );
+		$this->assertFalse( has_action( $tag, '__return_null' ) );
+
+		$wp_filter[ $tag ][ 11 ] = array( '__return_null' => array( 'function' => '__return_null', 'accepted_args' => 1 ) );
+		$this->assertEquals( 11, has_action( $tag, '__return_null' ) );
+	}
+
+	/**
 	 * Make sure current_action() behaves as current_filter()
 	 *
 	 * @ticket 14994
Index: tests/phpunit/tests/filters.php
===================================================================
--- tests/phpunit/tests/filters.php	(revision 38516)
+++ tests/phpunit/tests/filters.php	(working copy)
@@ -296,27 +296,6 @@
 	}
 
 	/**
-	 * @ticket 29070
-	 */
-	 function test_has_filter_doesnt_reset_wp_filter() {
-	 	add_action( 'action_test_has_filter_doesnt_reset_wp_filter', '__return_null', 1 );
-	 	add_action( 'action_test_has_filter_doesnt_reset_wp_filter', '__return_null', 2 );
-	 	add_action( 'action_test_has_filter_doesnt_reset_wp_filter', '__return_null', 3 );
-	 	add_action( 'action_test_has_filter_doesnt_reset_wp_filter', array( $this, '_action_test_has_filter_doesnt_reset_wp_filter' ), 4 );
-
-	 	do_action( 'action_test_has_filter_doesnt_reset_wp_filter' );
-	 }
-	 function _action_test_has_filter_doesnt_reset_wp_filter() {
-	 	global $wp_filter;
-
-	 	has_action( 'action_test_has_filter_doesnt_reset_wp_filter', '_function_that_doesnt_exist' );
-
-		$filters = current( $wp_filter['action_test_has_filter_doesnt_reset_wp_filter'] );
-	 	$the_ = current( $filters );
-	 	$this->assertEquals( $the_['function'], array( $this, '_action_test_has_filter_doesnt_reset_wp_filter' ) );
-	 }
-
-	/**
 	 * @ticket 10441
 	 * @expectedDeprecated tests_apply_filters_deprecated
 	 */
Index: tests/phpunit/tests/hooks/add_filter.php
===================================================================
--- tests/phpunit/tests/hooks/add_filter.php	(revision 0)
+++ tests/phpunit/tests/hooks/add_filter.php	(working copy)
@@ -0,0 +1,280 @@
+<?php
+
+
+/**
+ * Test the add_filter method of WP_Hook
+ *
+ * @group hooks
+ */
+class Tests_WP_Hook_Add_Filter extends WP_UnitTestCase {
+
+	public $hook;
+
+	public function test_add_filter_with_function() {
+		$callback = '__return_null';
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+
+		$hook->add_filter( $tag, $callback, $priority, $accepted_args );
+
+		$function_index = _wp_filter_build_unique_id( $tag, $callback, $priority );
+		$this->assertEquals( $callback, $hook->callbacks[ $priority ][ $function_index ]['function'] );
+		$this->assertEquals( $accepted_args, $hook->callbacks[ $priority ][ $function_index ]['accepted_args'] );
+	}
+
+	public function test_add_filter_with_object() {
+		$a = new MockAction();
+		$callback = array( $a, 'action' );
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+
+		$hook->add_filter( $tag, $callback, $priority, $accepted_args );
+
+		$function_index = _wp_filter_build_unique_id( $tag, $callback, $priority );
+		$this->assertEquals( $callback, $hook->callbacks[ $priority ][ $function_index ]['function'] );
+		$this->assertEquals( $accepted_args, $hook->callbacks[ $priority ][ $function_index ]['accepted_args'] );
+	}
+
+	public function test_add_filter_with_static_method() {
+		$callback = array( 'MockAction', 'action' );
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+
+		$hook->add_filter( $tag, $callback, $priority, $accepted_args );
+
+		$function_index = _wp_filter_build_unique_id( $tag, $callback, $priority );
+		$this->assertEquals( $callback, $hook->callbacks[ $priority ][ $function_index ]['function'] );
+		$this->assertEquals( $accepted_args, $hook->callbacks[ $priority ][ $function_index ]['accepted_args'] );
+	}
+
+	public function test_add_two_filters_with_same_priority() {
+		$callback_one = '__return_null';
+		$callback_two = '__return_false';
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+
+		$hook->add_filter( $tag, $callback_one, $priority, $accepted_args );
+		$this->assertCount( 1, $hook->callbacks[ $priority ] );
+
+		$hook->add_filter( $tag, $callback_two, $priority, $accepted_args );
+		$this->assertCount( 2, $hook->callbacks[ $priority ] );
+	}
+
+	public function test_add_two_filters_with_different_priority() {
+		$callback_one = '__return_null';
+		$callback_two = '__return_false';
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+
+		$hook->add_filter( $tag, $callback_one, $priority, $accepted_args );
+		$this->assertCount( 1, $hook->callbacks[ $priority ] );
+
+		$hook->add_filter( $tag, $callback_two, $priority + 1, $accepted_args );
+		$this->assertCount( 1, $hook->callbacks[ $priority ] );
+		$this->assertCount( 1, $hook->callbacks[ $priority + 1 ] );
+	}
+
+	public function test_readd_filter() {
+		$callback = '__return_null';
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+
+		$hook->add_filter( $tag, $callback, $priority, $accepted_args );
+		$this->assertCount( 1, $hook->callbacks[ $priority ] );
+
+		$hook->add_filter( $tag, $callback, $priority, $accepted_args );
+		$this->assertCount( 1, $hook->callbacks[ $priority ] );
+	}
+
+	public function test_readd_filter_with_different_priority() {
+		$callback = '__return_null';
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+
+		$hook->add_filter( $tag, $callback, $priority, $accepted_args );
+		$this->assertCount( 1, $hook->callbacks[ $priority ] );
+
+		$hook->add_filter( $tag, $callback, $priority + 1, $accepted_args );
+		$this->assertCount( 1, $hook->callbacks[ $priority ] );
+		$this->assertCount( 1, $hook->callbacks[ $priority + 1 ] );
+	}
+
+	public function test_sort_after_add_filter() {
+		$a = new MockAction();
+		$b = new MockAction();
+		$c = new MockAction();
+		$hook = new WP_Hook();
+		$tag = rand_str();
+
+		$hook->add_filter( $tag, array( $a, 'action' ), 10, 1 );
+		$hook->add_filter( $tag, array( $b, 'action' ), 5, 1 );
+		$hook->add_filter( $tag, array( $c, 'action' ), 8, 1 );
+
+		$this->assertEquals( array( 5, 8, 10 ), array_keys( $hook->callbacks ) );
+	}
+
+	public function test_remove_and_add() {
+		$this->hook = new Wp_Hook();
+
+		$this->hook->add_filter( 'remove_and_add', '__return_empty_string', 10, 0 );
+
+		$this->hook->add_filter( 'remove_and_add', array( $this, '_filter_remove_and_add2' ), 11, 1 );
+
+		$this->hook->add_filter( 'remove_and_add', array( $this, '_filter_remove_and_add4' ), 12, 1 );
+
+		$value = $this->hook->apply_filters( '', array() );
+
+		$this->assertSame( '24', $value );
+	}
+
+	public function test_remove_and_add_last_filter() {
+		$this->hook = new Wp_Hook();
+
+		$this->hook->add_filter( 'remove_and_add', '__return_empty_string', 10, 0 );
+
+		$this->hook->add_filter( 'remove_and_add', array( $this, '_filter_remove_and_add1' ), 11, 1 );
+
+		$this->hook->add_filter( 'remove_and_add', array( $this, '_filter_remove_and_add2' ), 12, 1 );
+
+		$value = $this->hook->apply_filters( '', array() );
+
+		$this->assertSame( '12', $value );
+	}
+
+	public function test_remove_and_recurse_and_add() {
+		$this->hook = new Wp_Hook();
+
+		$this->hook->add_filter( 'remove_and_add', '__return_empty_string', 10, 0 );
+
+		$this->hook->add_filter( 'remove_and_add', array( $this, '_filter_remove_and_add1' ), 11, 1 );
+		$this->hook->add_filter( 'remove_and_add', array( $this, '_filter_remove_and_recurse_and_add2' ), 11, 1 );
+		$this->hook->add_filter( 'remove_and_add', array( $this, '_filter_remove_and_add3' ), 11, 1 );
+
+		$this->hook->add_filter( 'remove_and_add', array( $this, '_filter_remove_and_add4' ), 12, 1 );
+
+		$value = $this->hook->apply_filters( '', array() );
+
+		$this->assertSame( '1-134-234', $value );
+	}
+
+	public function _filter_remove_and_add1( $string ) {
+		return $string . '1';
+	}
+
+	public function _filter_remove_and_add2( $string ) {
+		$this->hook->remove_filter( 'remove_and_add', array( $this, '_filter_remove_and_add2' ), 11 );
+		$this->hook->add_filter( 'remove_and_add', array( $this, '_filter_remove_and_add2' ), 11, 1 );
+
+		return $string . '2';
+	}
+
+	public function _filter_remove_and_recurse_and_add2( $string ) {
+		$this->hook->remove_filter( 'remove_and_add', array( $this, '_filter_remove_and_recurse_and_add2' ), 11 );
+
+		$string .= '-' . $this->hook->apply_filters( '', array() ) . '-';
+
+		$this->hook->add_filter( 'remove_and_add', array( $this, '_filter_remove_and_recurse_and_add2' ), 11, 1 );
+
+		return $string . '2';
+	}
+
+	public function _filter_remove_and_add3( $string ) {
+		return $string . '3';
+	}
+
+	public function _filter_remove_and_add4( $string ) {
+		return $string . '4';
+	}
+
+	public function test_remove_and_add_action() {
+		$this->hook = new Wp_Hook();
+		$this->action_output = '';
+
+		$this->hook->add_filter( 'remove_and_add_action', '__return_empty_string', 10, 0 );
+
+		$this->hook->add_filter( 'remove_and_add_action', array( $this, '_action_remove_and_add2' ), 11, 0 );
+
+		$this->hook->add_filter( 'remove_and_add_action', array( $this, '_action_remove_and_add4' ), 12, 0 );
+
+		$this->hook->do_action( array() );
+
+		$this->assertSame( '24', $this->action_output );
+	}
+
+	public function test_remove_and_add_last_action() {
+		$this->hook = new Wp_Hook();
+		$this->action_output = '';
+
+		$this->hook->add_filter( 'remove_and_add_action', '__return_empty_string', 10, 0 );
+
+		$this->hook->add_filter( 'remove_and_add_action', array( $this, '_action_remove_and_add1' ), 11, 0 );
+
+		$this->hook->add_filter( 'remove_and_add_action', array( $this, '_action_remove_and_add2' ), 12, 0 );
+
+		$this->hook->do_action( array() );
+
+		$this->assertSame( '12', $this->action_output );
+	}
+
+	public function test_remove_and_recurse_and_add_action() {
+		$this->hook = new Wp_Hook();
+		$this->action_output = '';
+
+		$this->hook->add_filter( 'remove_and_add_action', '__return_empty_string', 10, 0 );
+
+		$this->hook->add_filter( 'remove_and_add_action', array( $this, '_action_remove_and_add1' ), 11, 0 );
+		$this->hook->add_filter( 'remove_and_add_action', array( $this, '_action_remove_and_recurse_and_add2' ), 11, 0 );
+		$this->hook->add_filter( 'remove_and_add_action', array( $this, '_action_remove_and_add3' ), 11, 0 );
+
+		$this->hook->add_filter( 'remove_and_add_action', array( $this, '_action_remove_and_add4' ), 12, 0 );
+
+		$this->hook->do_action( array() );
+
+		$this->assertSame( '1-134-234', $this->action_output );
+	}
+
+	public function _action_remove_and_add1() {
+		$this->action_output .= 1;
+	}
+
+	public function _action_remove_and_add2() {
+		$this->hook->remove_filter( 'remove_and_add_action', array( $this, '_action_remove_and_add2' ), 11 );
+		$this->hook->add_filter( 'remove_and_add_action', array( $this, '_action_remove_and_add2' ), 11, 0 );
+
+		$this->action_output .= '2';
+	}
+
+	public function _action_remove_and_recurse_and_add2() {
+		$this->hook->remove_filter( 'remove_and_add_action', array( $this, '_action_remove_and_recurse_and_add2' ), 11 );
+
+		$this->action_output .= '-';
+		$this->hook->do_action( array() );
+		$this->action_output .= '-';
+
+		$this->hook->add_filter( 'remove_and_add_action', array( $this, '_action_remove_and_recurse_and_add2' ), 11, 0 );
+
+		$this->action_output .= '2';
+	}
+
+	public function _action_remove_and_add3() {
+		$this->action_output .= '3';
+	}
+
+	public function _action_remove_and_add4() {
+		$this->action_output .= '4';
+	}
+}

Property changes on: tests/phpunit/tests/hooks/add_filter.php
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: tests/phpunit/tests/hooks/apply_filters.php
===================================================================
--- tests/phpunit/tests/hooks/apply_filters.php	(revision 0)
+++ tests/phpunit/tests/hooks/apply_filters.php	(working copy)
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * Test the apply_filters method of WP_Hook
+ *
+ * @group hooks
+ */
+class Tests_WP_Hook_Apply_Filters extends WP_UnitTestCase {
+
+	public function test_apply_filters_with_callback() {
+		$a = new MockAction();
+		$callback = array( $a, 'filter' );
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+		$arg = rand_str();
+
+		$hook->add_filter( $tag, $callback, $priority, $accepted_args );
+
+		$returned = $hook->apply_filters( $arg, array( $arg ) );
+
+		$this->assertEquals( $returned, $arg );
+		$this->assertEquals( 1, $a->get_call_count() );
+	}
+
+	public function test_apply_filters_with_multiple_calls() {
+		$a = new MockAction();
+		$callback = array( $a, 'filter' );
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+		$arg = rand_str();
+
+		$hook->add_filter( $tag, $callback, $priority, $accepted_args );
+
+		$returned_one = $hook->apply_filters( $arg, array( $arg ) );
+		$returned_two = $hook->apply_filters( $returned_one, array( $returned_one ) );
+
+		$this->assertEquals( $returned_two, $arg );
+		$this->assertEquals( 2, $a->get_call_count() );
+	}
+
+}

Property changes on: tests/phpunit/tests/hooks/apply_filters.php
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: tests/phpunit/tests/hooks/do_action.php
===================================================================
--- tests/phpunit/tests/hooks/do_action.php	(revision 0)
+++ tests/phpunit/tests/hooks/do_action.php	(working copy)
@@ -0,0 +1,134 @@
+<?php
+
+/**
+ * Test the do_action method of WP_Hook
+ *
+ * @group hooks
+ */
+class Tests_WP_Hook_Do_Action extends WP_UnitTestCase {
+	private $events = array();
+
+	public function setUp() {
+		parent::setUp();
+		$this->events = array();
+	}
+
+	public function test_do_action_with_callback() {
+		$a = new MockAction();
+		$callback = array( $a, 'action' );
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+		$arg = rand_str();
+
+		$hook->add_filter( $tag, $callback, $priority, $accepted_args );
+		$hook->do_action( array( $arg ) );
+
+		$this->assertEquals( 1, $a->get_call_count() );
+	}
+
+	public function test_do_action_with_multiple_calls() {
+		$a = new MockAction();
+		$callback = array( $a, 'filter' );
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+		$arg = rand_str();
+
+		$hook->add_filter( $tag, $callback, $priority, $accepted_args );
+		$hook->do_action( array( $arg ) );
+		$hook->do_action( array( $arg ) );
+
+		$this->assertEquals( 2, $a->get_call_count() );
+	}
+
+	public function test_do_action_with_multiple_callbacks_on_same_priority() {
+		$a = new MockAction();
+		$b = new MockAction();
+		$callback_one = array( $a, 'filter' );
+		$callback_two = array( $b, 'filter' );
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+		$arg = rand_str();
+
+		$hook->add_filter( $tag, $callback_one, $priority, $accepted_args );
+		$hook->add_filter( $tag, $callback_two, $priority, $accepted_args );
+		$hook->do_action( array( $arg ) );
+
+		$this->assertEquals( 1, $a->get_call_count() );
+		$this->assertEquals( 1, $a->get_call_count() );
+	}
+
+	public function test_do_action_with_multiple_callbacks_on_different_priorities() {
+		$a = new MockAction();
+		$b = new MockAction();
+		$callback_one = array( $a, 'filter' );
+		$callback_two = array( $b, 'filter' );
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+		$arg = rand_str();
+
+		$hook->add_filter( $tag, $callback_one, $priority, $accepted_args );
+		$hook->add_filter( $tag, $callback_two, $priority, $accepted_args );
+		$hook->do_action( array( $arg ) );
+
+		$this->assertEquals( 1, $a->get_call_count() );
+		$this->assertEquals( 1, $a->get_call_count() );
+	}
+
+	public function test_do_action_with_no_accepted_args() {
+		$callback = array( $this, '_action_callback' );
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = 0;
+		$arg = rand_str();
+
+		$hook->add_filter( $tag, $callback, $priority, $accepted_args );
+		$hook->do_action( array( $arg ) );
+
+		$this->assertEmpty( $this->events[0]['args'] );
+	}
+
+	public function test_do_action_with_one_accepted_arg() {
+		$callback = array( $this, '_action_callback' );
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = 1;
+		$arg = rand_str();
+
+		$hook->add_filter( $tag, $callback, $priority, $accepted_args );
+		$hook->do_action( array( $arg ) );
+
+		$this->assertCount( 1, $this->events[0]['args'] );
+	}
+
+	public function test_do_action_with_more_accepted_args() {
+		$callback = array( $this, '_action_callback' );
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = 1000;
+		$arg = rand_str();
+
+		$hook->add_filter( $tag, $callback, $priority, $accepted_args );
+		$hook->do_action( array( $arg ) );
+
+		$this->assertCount( 1, $this->events[0]['args'] );
+	}
+
+	/**
+	 * Use this rather than MockAction so we can test callbacks with no args
+	 */
+	public function _action_callback() {
+		$args = func_get_args();
+		$this->events[] = array('action' => __FUNCTION__, 'args'=>$args);
+	}
+}

Property changes on: tests/phpunit/tests/hooks/do_action.php
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: tests/phpunit/tests/hooks/do_all_hook.php
===================================================================
--- tests/phpunit/tests/hooks/do_all_hook.php	(revision 0)
+++ tests/phpunit/tests/hooks/do_all_hook.php	(working copy)
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * Test the do_all_hook method of WP_Hook
+ *
+ * @group hooks
+ */
+class Tests_WP_Hook_Do_All_Hook extends WP_UnitTestCase {
+
+	public function test_do_all_hook_with_multiple_calls() {
+		$a = new MockAction();
+		$callback = array( $a, 'action' );
+		$hook = new WP_Hook();
+		$tag = 'all';
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+		$arg = rand_str();
+
+		$hook->add_filter( $tag, $callback, $priority, $accepted_args );
+		$args = array( $arg );
+		$hook->do_all_hook( $args );
+		$hook->do_all_hook( $args );
+
+		$this->assertEquals( 2, $a->get_call_count() );
+	}
+}

Property changes on: tests/phpunit/tests/hooks/do_all_hook.php
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: tests/phpunit/tests/hooks/has_filter.php
===================================================================
--- tests/phpunit/tests/hooks/has_filter.php	(revision 0)
+++ tests/phpunit/tests/hooks/has_filter.php	(working copy)
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * Test the has_filter method of WP_Hook
+ *
+ * @group hooks
+ */
+class Tests_WP_Hook_Has_Filter extends WP_UnitTestCase {
+
+	public function test_has_filter_with_function() {
+		$callback = '__return_null';
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+
+		$hook->add_filter( $tag, $callback, $priority, $accepted_args );
+
+		$this->assertEquals( $priority, $hook->has_filter( $tag, $callback ) );
+	}
+
+	public function test_has_filter_with_object() {
+		$a = new MockAction();
+		$callback = array( $a, 'action' );
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+
+		$hook->add_filter( $tag, $callback, $priority, $accepted_args );
+
+		$this->assertEquals( $priority, $hook->has_filter( $tag, $callback ) );
+	}
+
+	public function test_has_filter_with_static_method() {
+		$callback = array( 'MockAction', 'action' );
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+
+		$hook->add_filter( $tag, $callback, $priority, $accepted_args );
+
+		$this->assertEquals( $priority, $hook->has_filter( $tag, $callback ) );
+	}
+
+	public function test_has_filter_without_callback() {
+		$callback = '__return_null';
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+
+		$hook->add_filter( $tag, $callback, $priority, $accepted_args );
+
+		$this->assertTrue( $hook->has_filter() );
+	}
+
+	public function test_not_has_filter_without_callback() {
+		$hook = new WP_Hook();
+		$this->assertFalse( $hook->has_filter() );
+	}
+
+	public function test_not_has_filter_with_callback() {
+		$callback = '__return_null';
+		$hook = new WP_Hook();
+		$tag = rand_str();
+
+		$this->assertFalse( $hook->has_filter( $tag, $callback ) );
+	}
+
+	public function test_has_filter_with_wrong_callback() {
+		$callback = '__return_null';
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+
+		$hook->add_filter( $tag, $callback, $priority, $accepted_args );
+
+		$this->assertFalse( $hook->has_filter( $tag, '__return_false' ) );
+	}
+}

Property changes on: tests/phpunit/tests/hooks/has_filter.php
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: tests/phpunit/tests/hooks/has_filters.php
===================================================================
--- tests/phpunit/tests/hooks/has_filters.php	(revision 0)
+++ tests/phpunit/tests/hooks/has_filters.php	(working copy)
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * Test the has_filters method of WP_Hook
+ *
+ * @group hooks
+ */
+class Tests_WP_Hook_Has_Filters extends WP_UnitTestCase {
+
+	public function test_has_filters_with_callback() {
+		$callback = '__return_null';
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+
+		$hook->add_filter( $tag, $callback, $priority, $accepted_args );
+
+		$this->assertTrue( $hook->has_filters() );
+	}
+
+	public function test_has_filters_without_callback() {
+		$hook = new WP_Hook();
+		$this->assertFalse( $hook->has_filters() );
+	}
+
+	public function test_not_has_filters_with_removed_callback() {
+		$callback = '__return_null';
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+
+		$hook->add_filter( $tag, $callback, $priority, $accepted_args );
+		$hook->remove_filter( $tag, $callback, $priority );
+		$this->assertFalse( $hook->has_filters() );
+	}
+
+	public function test_not_has_filter_with_directly_removed_callback() {
+		$callback = '__return_null';
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+
+		$hook->add_filter( $tag, $callback, $priority, $accepted_args );
+		$function_key = _wp_filter_build_unique_id( $tag, $callback, $priority );
+		unset( $hook->callbacks[ $priority ][ $function_key ] );
+
+		$this->assertFalse( $hook->has_filters() );
+	}
+}

Property changes on: tests/phpunit/tests/hooks/has_filters.php
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: tests/phpunit/tests/hooks/iterator.php
===================================================================
--- tests/phpunit/tests/hooks/iterator.php	(revision 0)
+++ tests/phpunit/tests/hooks/iterator.php	(working copy)
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * Test the Iterator implementation of WP_Hook
+ *
+ * @group hooks
+ */
+class Tests_WP_Hook_Iterator extends WP_UnitTestCase {
+
+	public function test_foreach() {
+		$callback_one = '__return_null';
+		$callback_two = '__return_false';
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+
+		$hook->add_filter( $tag, $callback_one, $priority, $accepted_args );
+		$hook->add_filter( $tag, $callback_two, $priority + 1, $accepted_args );
+
+		$functions = array();
+		$priorities = array();
+		foreach ( $hook as $key => $callbacks ) {
+			$priorities[] = $key;
+			foreach ( $callbacks as $function_index => $the_ ) {
+				$functions[] = $the_['function'];
+			}
+		}
+		$this->assertEqualSets( array( $priority, $priority + 1 ), $priorities );
+		$this->assertEqualSets( array( $callback_one, $callback_two ), $functions );
+	}
+}

Property changes on: tests/phpunit/tests/hooks/iterator.php
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: tests/phpunit/tests/hooks/preinit_hooks.php
===================================================================
--- tests/phpunit/tests/hooks/preinit_hooks.php	(revision 0)
+++ tests/phpunit/tests/hooks/preinit_hooks.php	(working copy)
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * Test the IteratorAggregate implementation of WP_Hook
+ *
+ * @group hooks
+ */
+class Tests_WP_Hook_Preinit_Hooks extends WP_UnitTestCase {
+
+	public function test_array_to_hooks() {
+		$tag1 = rand_str();
+		$priority1 = rand( 1, 100 );
+		$tag2 = rand_str();
+		$priority2 = rand( 1, 100 );
+		$filters = array(
+			$tag1 => array(
+				$priority1 => array(
+					'test1' => array(
+						'function' => '__return_false',
+						'accepted_args' => 2,
+					),
+				),
+			),
+			$tag2 => array(
+				$priority2 => array(
+					'test1' => array(
+						'function' => '__return_null',
+						'accepted_args' => 1,
+					),
+				),
+			),
+		);
+
+		$hooks = WP_Hook::build_preinitialized_hooks( $filters );
+
+		$this->assertEquals( $priority1, $hooks[ $tag1 ]->has_filter( $tag1, '__return_false' ) );
+		$this->assertEquals( $priority2, $hooks[ $tag2 ]->has_filter( $tag2, '__return_null' ) );
+	}
+}

Property changes on: tests/phpunit/tests/hooks/preinit_hooks.php
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: tests/phpunit/tests/hooks/remove_all_filters.php
===================================================================
--- tests/phpunit/tests/hooks/remove_all_filters.php	(revision 0)
+++ tests/phpunit/tests/hooks/remove_all_filters.php	(working copy)
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * Test the remove_all_filters method of WP_Hook
+ *
+ * @group hooks
+ */
+class Tests_WP_Hook_Remove_All_Filters extends WP_UnitTestCase {
+
+	public function test_remove_all_filters() {
+		$callback = '__return_null';
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+
+		$hook->add_filter( $tag, $callback, $priority, $accepted_args );
+
+		$hook->remove_all_filters();
+
+		$this->assertFalse( $hook->has_filters() );
+	}
+
+	public function test_remove_all_filters_with_priority() {
+		$callback_one = '__return_null';
+		$callback_two = '__return_false';
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+
+		$hook->add_filter( $tag, $callback_one, $priority, $accepted_args );
+		$hook->add_filter( $tag, $callback_two, $priority + 1, $accepted_args );
+
+		$hook->remove_all_filters( $priority );
+
+		$this->assertFalse( $hook->has_filter( $tag, $callback_one ) );
+		$this->assertTrue( $hook->has_filters() );
+		$this->assertEquals( $priority + 1, $hook->has_filter( $tag, $callback_two ) );
+	}
+}

Property changes on: tests/phpunit/tests/hooks/remove_all_filters.php
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: tests/phpunit/tests/hooks/remove_filter.php
===================================================================
--- tests/phpunit/tests/hooks/remove_filter.php	(revision 0)
+++ tests/phpunit/tests/hooks/remove_filter.php	(working copy)
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * Test the remove_filter method of WP_Hook
+ *
+ * @group hooks
+ */
+class Tests_WP_Hook_Remove_Filter extends WP_UnitTestCase {
+
+	public function test_remove_filter_with_function() {
+		$callback = '__return_null';
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+
+		$hook->add_filter( $tag, $callback, $priority, $accepted_args );
+		$hook->remove_filter( $tag, $callback, $priority );
+
+		$this->assertFalse( isset( $hook->callbacks[ $priority ] ) );
+	}
+
+	public function test_remove_filter_with_object() {
+		$a = new MockAction();
+		$callback = array( $a, 'action' );
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+
+		$hook->add_filter( $tag, $callback, $priority, $accepted_args );
+		$hook->remove_filter( $tag, $callback, $priority );
+
+		$this->assertFalse( isset( $hook->callbacks[ $priority ] ) );
+	}
+
+	public function test_remove_filter_with_static_method() {
+		$callback = array( 'MockAction', 'action' );
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+
+		$hook->add_filter( $tag, $callback, $priority, $accepted_args );
+		$hook->remove_filter( $tag, $callback, $priority );
+
+		$this->assertFalse( isset( $hook->callbacks[ $priority ] ) );
+	}
+
+	public function test_remove_filters_with_another_at_same_priority() {
+		$callback_one = '__return_null';
+		$callback_two = '__return_false';
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+
+		$hook->add_filter( $tag, $callback_one, $priority, $accepted_args );
+		$hook->add_filter( $tag, $callback_two, $priority, $accepted_args );
+
+		$hook->remove_filter( $tag, $callback_one, $priority );
+
+		$this->assertCount( 1, $hook->callbacks[ $priority ] );
+	}
+
+	public function test_remove_filter_with_another_at_different_priority() {
+		$callback_one = '__return_null';
+		$callback_two = '__return_false';
+		$hook = new WP_Hook();
+		$tag = rand_str();
+		$priority = rand( 1, 100 );
+		$accepted_args = rand( 1, 100 );
+
+		$hook->add_filter( $tag, $callback_one, $priority, $accepted_args );
+		$hook->add_filter( $tag, $callback_two, $priority + 1, $accepted_args );
+
+		$hook->remove_filter( $tag, $callback_one, $priority );
+		$this->assertFalse( isset( $hook->callbacks[ $priority ] ) );
+		$this->assertCount( 1, $hook->callbacks[ $priority + 1 ] );
+	}
+}

Property changes on: tests/phpunit/tests/hooks/remove_filter.php
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: tests/phpunit/tests/post/types.php
===================================================================
--- tests/phpunit/tests/post/types.php	(revision 38516)
+++ tests/phpunit/tests/post/types.php	(working copy)
@@ -446,7 +446,7 @@
 
 		$this->assertSame( 1, count( $wp_filter['future_foo'] ) );
 		$this->assertTrue( unregister_post_type( 'foo' ) );
-		$this->assertSame( array(), $wp_filter['future_foo'] );
+		$this->assertArrayNotHasKey( 'future_foo', $wp_filter );
 	}
 
 	/**
@@ -462,7 +462,7 @@
 
 		$this->assertSame( 1, count( $wp_filter['add_meta_boxes_foo'] ) );
 		$this->assertTrue( unregister_post_type( 'foo' ) );
-		$this->assertSame( array(), $wp_filter['add_meta_boxes_foo'] );
+		$this->assertArrayNotHasKey( 'add_meta_boxes_foo', $wp_filter );
 	}
 
 	/**
Index: tests/phpunit/tests/taxonomy.php
===================================================================
--- tests/phpunit/tests/taxonomy.php	(revision 38516)
+++ tests/phpunit/tests/taxonomy.php	(working copy)
@@ -697,7 +697,7 @@
 
 		$this->assertSame( 1, count( $wp_filter['wp_ajax_add-foo'] ) );
 		$this->assertTrue( unregister_taxonomy( 'foo' ) );
-		$this->assertSame( array(), $wp_filter['wp_ajax_add-foo'] );
+		$this->assertArrayNotHasKey( 'wp_ajax_add-foo', $wp_filter );
 	}
 
 	/**
