summaryrefslogtreecommitdiffstatshomepage
path: root/tests/phpunit/includes
diff options
context:
space:
mode:
authorAndrew Nacin <nacin@git.wordpress.org>2013-08-29 18:39:34 +0000
committerAndrew Nacin <nacin@git.wordpress.org>2013-08-29 18:39:34 +0000
commit8045afd81b7c80f6ef5b327c115a5bbb43e4b65c (patch)
tree15d457007610c451577debda89bd9e9cd3d74551 /tests/phpunit/includes
parentd34baebc1d8111c9c1014e11001957face778e52 (diff)
downloadwordpress-8045afd81b7c80f6ef5b327c115a5bbb43e4b65c.tar.gz
wordpress-8045afd81b7c80f6ef5b327c115a5bbb43e4b65c.zip
Move PHPUnit tests into a tests/phpunit directory.
wp-tests-config.php can/should reside in the root of a develop checkout. `phpunit` should be run from the root. see #25088. git-svn-id: https://develop.svn.wordpress.org/trunk@25165 602fd350-edb4-49c9-b593-d223f7449a82
Diffstat (limited to 'tests/phpunit/includes')
-rw-r--r--tests/phpunit/includes/bootstrap.php135
-rw-r--r--tests/phpunit/includes/exceptions.php33
-rw-r--r--tests/phpunit/includes/factory.php344
-rw-r--r--tests/phpunit/includes/functions.php44
-rw-r--r--tests/phpunit/includes/install.php67
-rw-r--r--tests/phpunit/includes/mock-fs.php226
-rw-r--r--tests/phpunit/includes/mock-image-editor.php43
-rw-r--r--tests/phpunit/includes/mock-mailer.php26
-rw-r--r--tests/phpunit/includes/testcase-ajax.php182
-rw-r--r--tests/phpunit/includes/testcase-xmlrpc.php30
-rw-r--r--tests/phpunit/includes/testcase.php227
-rw-r--r--tests/phpunit/includes/trac.php54
-rw-r--r--tests/phpunit/includes/utils.php365
-rw-r--r--tests/phpunit/includes/wp-profiler.php216
14 files changed, 1992 insertions, 0 deletions
diff --git a/tests/phpunit/includes/bootstrap.php b/tests/phpunit/includes/bootstrap.php
new file mode 100644
index 0000000000..b73dd4e09c
--- /dev/null
+++ b/tests/phpunit/includes/bootstrap.php
@@ -0,0 +1,135 @@
+<?php
+/**
+ * Installs WordPress for running the tests and loads WordPress and the test libraries
+ */
+
+
+$config_file_path = dirname( dirname( __FILE__ ) );
+if ( ! file_exists( $config_file_path . '/wp-tests-config.php' ) ) {
+ // Support the config file from the root of the develop repository.
+ if ( basename( $config_file_path ) === 'phpunit' && basename( dirname( $config_file_path ) ) === 'tests' )
+ $config_file_path = dirname( dirname( $config_file_path ) );
+}
+$config_file_path .= '/wp-tests-config.php';
+
+/*
+ * Globalize some WordPress variables, because PHPUnit loads this file inside a function
+ * See: https://github.com/sebastianbergmann/phpunit/issues/325
+ */
+global $wpdb, $current_site, $current_blog, $wp_rewrite, $shortcode_tags, $wp, $phpmailer;
+
+if ( !is_readable( $config_file_path ) ) {
+ die( "ERROR: wp-tests-config.php is missing! Please use wp-tests-config-sample.php to create a config file.\n" );
+}
+require_once $config_file_path;
+
+define( 'DIR_TESTDATA', dirname( __FILE__ ) . '/../data' );
+
+if ( ! defined( 'WP_TESTS_FORCE_KNOWN_BUGS' ) )
+ define( 'WP_TESTS_FORCE_KNOWN_BUGS', false );
+
+// Cron tries to make an HTTP request to the blog, which always fails, because tests are run in CLI mode only
+define( 'DISABLE_WP_CRON', true );
+
+define( 'WP_MEMORY_LIMIT', -1 );
+define( 'WP_MAX_MEMORY_LIMIT', -1 );
+
+$_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1';
+$_SERVER['HTTP_HOST'] = WP_TESTS_DOMAIN;
+$PHP_SELF = $GLOBALS['PHP_SELF'] = $_SERVER['PHP_SELF'] = '/index.php';
+
+if ( "1" == getenv( 'WP_MULTISITE' ) ||
+ ( defined( 'WP_TESTS_MULTISITE') && WP_TESTS_MULTISITE ) ) {
+ $multisite = true;
+} else {
+ $multisite = false;
+}
+
+// Override the PHPMailer
+require_once( dirname( __FILE__ ) . '/mock-mailer.php' );
+$phpmailer = new MockPHPMailer();
+
+system( WP_PHP_BINARY . ' ' . escapeshellarg( dirname( __FILE__ ) . '/install.php' ) . ' ' . escapeshellarg( $config_file_path ) . ' ' . $multisite );
+
+if ( $multisite ) {
+ echo "Running as multisite..." . PHP_EOL;
+ define( 'MULTISITE', true );
+ define( 'SUBDOMAIN_INSTALL', false );
+ define( 'DOMAIN_CURRENT_SITE', WP_TESTS_DOMAIN );
+ define( 'PATH_CURRENT_SITE', '/' );
+ define( 'SITE_ID_CURRENT_SITE', 1 );
+ define( 'BLOG_ID_CURRENT_SITE', 1 );
+ $GLOBALS['base'] = '/';
+} else {
+ echo "Running as single site... To run multisite, use -c multisite.xml" . PHP_EOL;
+}
+unset( $multisite );
+
+require_once dirname( __FILE__ ) . '/functions.php';
+
+// Preset WordPress options defined in bootstrap file.
+// Used to activate themes, plugins, as well as other settings.
+if(isset($GLOBALS['wp_tests_options'])) {
+ function wp_tests_options( $value ) {
+ $key = substr( current_filter(), strlen( 'pre_option_' ) );
+ return $GLOBALS['wp_tests_options'][$key];
+ }
+
+ foreach ( array_keys( $GLOBALS['wp_tests_options'] ) as $key ) {
+ tests_add_filter( 'pre_option_'.$key, 'wp_tests_options' );
+ }
+}
+
+// Load WordPress
+require_once ABSPATH . '/wp-settings.php';
+
+// Delete any default posts & related data
+_delete_all_posts();
+
+require dirname( __FILE__ ) . '/testcase.php';
+require dirname( __FILE__ ) . '/testcase-xmlrpc.php';
+require dirname( __FILE__ ) . '/testcase-ajax.php';
+require dirname( __FILE__ ) . '/exceptions.php';
+require dirname( __FILE__ ) . '/utils.php';
+
+/**
+ * A child class of the PHP test runner.
+ *
+ * Not actually used as a runner. Rather, used to access the protected
+ * longOptions property, to parse the arguments passed to the script.
+ *
+ * If it is determined that phpunit was called with a --group that corresponds
+ * to an @ticket annotation (such as `phpunit --group 12345` for bugs marked
+ * as #WP12345), then it is assumed that known bugs should not be skipped.
+ *
+ * If WP_TESTS_FORCE_KNOWN_BUGS is already set in wp-tests-config.php, then
+ * how you call phpunit has no effect.
+ */
+class WP_PHPUnit_TextUI_Command extends PHPUnit_TextUI_Command {
+ function __construct( $argv ) {
+ $options = PHPUnit_Util_Getopt::getopt(
+ $argv,
+ 'd:c:hv',
+ array_keys( $this->longOptions )
+ );
+ $ajax_message = true;
+ foreach ( $options[0] as $option ) {
+ switch ( $option[0] ) {
+ case '--exclude-group' :
+ $ajax_message = false;
+ continue 2;
+ case '--group' :
+ $groups = explode( ',', $option[1] );
+ foreach ( $groups as $group ) {
+ if ( is_numeric( $group ) || preg_match( '/^(UT|Plugin)\d+$/', $group ) )
+ WP_UnitTestCase::forceTicket( $group );
+ }
+ $ajax_message = ! in_array( 'ajax', $groups );
+ continue 2;
+ }
+ }
+ if ( $ajax_message )
+ echo "Not running ajax tests... To execute these, use --group ajax." . PHP_EOL;
+ }
+}
+new WP_PHPUnit_TextUI_Command( $_SERVER['argv'] );
diff --git a/tests/phpunit/includes/exceptions.php b/tests/phpunit/includes/exceptions.php
new file mode 100644
index 0000000000..50976fb0b3
--- /dev/null
+++ b/tests/phpunit/includes/exceptions.php
@@ -0,0 +1,33 @@
+<?php
+
+class WP_Tests_Exception extends PHPUnit_Framework_Exception {
+
+}
+
+/**
+ * General exception for wp_die()
+ */
+class WPDieException extends Exception {}
+
+/**
+ * Exception for cases of wp_die(), for ajax tests.
+ * This means there was an error (no output, and a call to wp_die)
+ *
+ * @package WordPress
+ * @subpackage Unit Tests
+ * @since 3.4.0
+ */
+class WPAjaxDieStopException extends WPDieException {}
+
+/**
+ * Exception for cases of wp_die(), for ajax tests.
+ * This means execution of the ajax function should be halted, but the unit
+ * test can continue. The function finished normally and there was not an
+ * error (output happened, but wp_die was called to end execution) This is
+ * used with WP_Ajax_Response::send
+ *
+ * @package WordPress
+ * @subpackage Unit Tests
+ * @since 3.4.0
+ */
+class WPAjaxDieContinueException extends WPDieException {}
diff --git a/tests/phpunit/includes/factory.php b/tests/phpunit/includes/factory.php
new file mode 100644
index 0000000000..bf5ba53777
--- /dev/null
+++ b/tests/phpunit/includes/factory.php
@@ -0,0 +1,344 @@
+<?php
+
+class WP_UnitTest_Factory {
+
+ /**
+ * @var WP_UnitTest_Factory_For_Post
+ */
+ public $post;
+
+ /**
+ * @var WP_UnitTest_Factory_For_Attachment
+ */
+ public $attachment;
+
+ /**
+ * @var WP_UnitTest_Factory_For_Comment
+ */
+ public $comment;
+
+ /**
+ * @var WP_UnitTest_Factory_For_User
+ */
+ public $user;
+
+ /**
+ * @var WP_UnitTest_Factory_For_Term
+ */
+ public $term;
+
+ /**
+ * @var WP_UnitTest_Factory_For_Term
+ */
+ public $category;
+
+ /**
+ * @var WP_UnitTest_Factory_For_Term
+ */
+ public $tag;
+
+ /**
+ * @var WP_UnitTest_Factory_For_Blog
+ */
+ public $blog;
+
+ function __construct() {
+ $this->post = new WP_UnitTest_Factory_For_Post( $this );
+ $this->attachment = new WP_UnitTest_Factory_For_Attachment( $this );
+ $this->comment = new WP_UnitTest_Factory_For_Comment( $this );
+ $this->user = new WP_UnitTest_Factory_For_User( $this );
+ $this->term = new WP_UnitTest_Factory_For_Term( $this );
+ $this->category = new WP_UnitTest_Factory_For_Term( $this, 'category' );
+ $this->tag = new WP_UnitTest_Factory_For_Term( $this, 'post_tag' );
+ if ( is_multisite() )
+ $this->blog = new WP_UnitTest_Factory_For_Blog( $this );
+ }
+}
+
+class WP_UnitTest_Factory_For_Post extends WP_UnitTest_Factory_For_Thing {
+
+ function __construct( $factory = null ) {
+ parent::__construct( $factory );
+ $this->default_generation_definitions = array(
+ 'post_status' => 'publish',
+ 'post_title' => new WP_UnitTest_Generator_Sequence( 'Post title %s' ),
+ 'post_content' => new WP_UnitTest_Generator_Sequence( 'Post content %s' ),
+ 'post_excerpt' => new WP_UnitTest_Generator_Sequence( 'Post excerpt %s' ),
+ 'post_type' => 'post'
+ );
+ }
+
+ function create_object( $args ) {
+ return wp_insert_post( $args );
+ }
+
+ function update_object( $post_id, $fields ) {
+ $fields['ID'] = $post_id;
+ return wp_update_post( $fields );
+ }
+
+ function get_object_by_id( $post_id ) {
+ return get_post( $post_id );
+ }
+}
+
+class WP_UnitTest_Factory_For_Attachment extends WP_UnitTest_Factory_For_Post {
+
+ function create_object( $file, $parent = 0, $args = array() ) {
+ return wp_insert_attachment( $args, $file, $parent );
+ }
+}
+
+class WP_UnitTest_Factory_For_User extends WP_UnitTest_Factory_For_Thing {
+
+ function __construct( $factory = null ) {
+ parent::__construct( $factory );
+ $this->default_generation_definitions = array(
+ 'user_login' => new WP_UnitTest_Generator_Sequence( 'User %s' ),
+ 'user_pass' => 'password',
+ 'user_email' => new WP_UnitTest_Generator_Sequence( 'user_%s@example.org' ),
+ );
+ }
+
+ function create_object( $args ) {
+ return wp_insert_user( $args );
+ }
+
+ function update_object( $user_id, $fields ) {
+ $fields['ID'] = $user_id;
+ return wp_update_user( $fields );
+ }
+
+ function get_object_by_id( $user_id ) {
+ return new WP_User( $user_id );
+ }
+}
+
+class WP_UnitTest_Factory_For_Comment extends WP_UnitTest_Factory_For_Thing {
+
+ function __construct( $factory = null ) {
+ parent::__construct( $factory );
+ $this->default_generation_definitions = array(
+ 'comment_author' => new WP_UnitTest_Generator_Sequence( 'Commenter %s' ),
+ 'comment_author_url' => new WP_UnitTest_Generator_Sequence( 'http://example.com/%s/' ),
+ 'comment_approved' => 1,
+ );
+ }
+
+ function create_object( $args ) {
+ return wp_insert_comment( $this->addslashes_deep( $args ) );
+ }
+
+ function update_object( $comment_id, $fields ) {
+ $fields['comment_ID'] = $comment_id;
+ return wp_update_comment( $this->addslashes_deep( $fields ) );
+ }
+
+ function create_post_comments( $post_id, $count = 1, $args = array(), $generation_definitions = null ) {
+ $args['comment_post_ID'] = $post_id;
+ return $this->create_many( $count, $args, $generation_definitions );
+ }
+
+ function get_object_by_id( $comment_id ) {
+ return get_comment( $comment_id );
+ }
+}
+
+class WP_UnitTest_Factory_For_Blog extends WP_UnitTest_Factory_For_Thing {
+
+ function __construct( $factory = null ) {
+ global $current_site, $base;
+ parent::__construct( $factory );
+ $this->default_generation_definitions = array(
+ 'domain' => $current_site->domain,
+ 'path' => new WP_UnitTest_Generator_Sequence( $base . 'testpath%s' ),
+ 'title' => new WP_UnitTest_Generator_Sequence( 'Site %s' ),
+ 'site_id' => $current_site->id,
+ );
+ }
+
+ function create_object( $args ) {
+ $meta = isset( $args['meta'] ) ? $args['meta'] : array();
+ $user_id = isset( $args['user_id'] ) ? $args['user_id'] : get_current_user_id();
+ return wpmu_create_blog( $args['domain'], $args['path'], $args['title'], $user_id, $meta, $args['site_id'] );
+ }
+
+ function update_object( $blog_id, $fields ) {}
+
+ function get_object_by_id( $blog_id ) {
+ return get_blog_details( $blog_id, false );
+ }
+}
+
+
+class WP_UnitTest_Factory_For_Term extends WP_UnitTest_Factory_For_Thing {
+
+ private $taxonomy;
+ const DEFAULT_TAXONOMY = 'post_tag';
+
+ function __construct( $factory = null, $taxonomy = null ) {
+ parent::__construct( $factory );
+ $this->taxonomy = $taxonomy ? $taxonomy : self::DEFAULT_TAXONOMY;
+ $this->default_generation_definitions = array(
+ 'name' => new WP_UnitTest_Generator_Sequence( 'Term %s' ),
+ 'taxonomy' => $this->taxonomy,
+ 'description' => new WP_UnitTest_Generator_Sequence( 'Term description %s' ),
+ );
+ }
+
+ function create_object( $args ) {
+ $args = array_merge( array( 'taxonomy' => $this->taxonomy ), $args );
+ $term_id_pair = wp_insert_term( $args['name'], $args['taxonomy'], $args );
+ if ( is_wp_error( $term_id_pair ) )
+ return $term_id_pair;
+ return $term_id_pair['term_id'];
+ }
+
+ function update_object( $term, $fields ) {
+ $fields = array_merge( array( 'taxonomy' => $this->taxonomy ), $fields );
+ if ( is_object( $term ) )
+ $taxonomy = $term->taxonomy;
+ $term_id_pair = wp_update_term( $term, $taxonomy, $fields );
+ return $term_id_pair['term_id'];
+ }
+
+ function add_post_terms( $post_id, $terms, $taxonomy, $append = true ) {
+ return wp_set_post_terms( $post_id, $terms, $taxonomy, $append );
+ }
+
+ function get_object_by_id( $term_id ) {
+ return get_term( $term_id, $this->taxonomy );
+ }
+}
+
+abstract class WP_UnitTest_Factory_For_Thing {
+
+ var $default_generation_definitions;
+ var $factory;
+
+ /**
+ * Creates a new factory, which will create objects of a specific Thing
+ *
+ * @param object $factory Global factory that can be used to create other objects on the system
+ * @param array $default_generation_definitions Defines what default values should the properties of the object have. The default values
+ * can be generators -- an object with next() method. There are some default generators: {@link WP_UnitTest_Generator_Sequence},
+ * {@link WP_UnitTest_Generator_Locale_Name}, {@link WP_UnitTest_Factory_Callback_After_Create}.
+ */
+ function __construct( $factory, $default_generation_definitions = array() ) {
+ $this->factory = $factory;
+ $this->default_generation_definitions = $default_generation_definitions;
+ }
+
+ abstract function create_object( $args );
+ abstract function update_object( $object, $fields );
+
+ function create( $args = array(), $generation_definitions = null ) {
+ if ( is_null( $generation_definitions ) )
+ $generation_definitions = $this->default_generation_definitions;
+
+ $generated_args = $this->generate_args( $args, $generation_definitions, $callbacks );
+ $created = $this->create_object( $generated_args );
+ if ( !$created || is_wp_error( $created ) )
+ return $created;
+
+ if ( $callbacks ) {
+ $updated_fields = $this->apply_callbacks( $callbacks, $created );
+ $save_result = $this->update_object( $created, $updated_fields );
+ if ( !$save_result || is_wp_error( $save_result ) )
+ return $save_result;
+ }
+ return $created;
+ }
+
+ function create_and_get( $args = array(), $generation_definitions = null ) {
+ $object_id = $this->create( $args, $generation_definitions );
+ return $this->get_object_by_id( $object_id );
+ }
+
+ abstract function get_object_by_id( $object_id );
+
+ function create_many( $count, $args = array(), $generation_definitions = null ) {
+ $results = array();
+ for ( $i = 0; $i < $count; $i++ ) {
+ $results[] = $this->create( $args, $generation_definitions );
+ }
+ return $results;
+ }
+
+ function generate_args( $args = array(), $generation_definitions = null, &$callbacks = null ) {
+ $callbacks = array();
+ if ( is_null( $generation_definitions ) )
+ $generation_definitions = $this->default_generation_definitions;
+
+ foreach( array_keys( $generation_definitions ) as $field_name ) {
+ if ( !isset( $args[$field_name] ) ) {
+ $generator = $generation_definitions[$field_name];
+ if ( is_scalar( $generator ) )
+ $args[$field_name] = $generator;
+ elseif ( is_object( $generator ) && method_exists( $generator, 'call' ) ) {
+ $callbacks[$field_name] = $generator;
+ } elseif ( is_object( $generator ) )
+ $args[$field_name] = $generator->next();
+ else
+ return new WP_Error( 'invalid_argument', 'Factory default value should be either a scalar or an generator object.' );
+ }
+ }
+ return $args;
+ }
+
+ function apply_callbacks( $callbacks, $created ) {
+ $updated_fields = array();
+ foreach( $callbacks as $field_name => $generator ) {
+ $updated_fields[$field_name] = $generator->call( $created );
+ }
+ return $updated_fields;
+ }
+
+ function callback( $function ) {
+ return new WP_UnitTest_Factory_Callback_After_Create( $function );
+ }
+
+ function addslashes_deep($value) {
+ if ( is_array( $value ) ) {
+ $value = array_map( array( $this, 'addslashes_deep' ), $value );
+ } elseif ( is_object( $value ) ) {
+ $vars = get_object_vars( $value );
+ foreach ($vars as $key=>$data) {
+ $value->{$key} = $this->addslashes_deep( $data );
+ }
+ } elseif ( is_string( $value ) ) {
+ $value = addslashes( $value );
+ }
+
+ return $value;
+ }
+
+}
+
+class WP_UnitTest_Generator_Sequence {
+ var $next;
+ var $template_string;
+
+ function __construct( $template_string = '%s', $start = 1 ) {
+ $this->next = $start;
+ $this->template_string = $template_string;
+ }
+
+ function next() {
+ $generated = sprintf( $this->template_string , $this->next );
+ $this->next++;
+ return $generated;
+ }
+}
+
+class WP_UnitTest_Factory_Callback_After_Create {
+ var $callback;
+
+ function __construct( $callback ) {
+ $this->callback = $callback;
+ }
+
+ function call( $object ) {
+ return call_user_func( $this->callback, $object );
+ }
+}
diff --git a/tests/phpunit/includes/functions.php b/tests/phpunit/includes/functions.php
new file mode 100644
index 0000000000..5759d530c9
--- /dev/null
+++ b/tests/phpunit/includes/functions.php
@@ -0,0 +1,44 @@
+<?php
+
+// For adding hooks before loading WP
+function tests_add_filter($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
+ global $wp_filter, $merged_filters;
+
+ $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;
+
+ if ( is_object($function) ) {
+ // Closures are currently implemented as objects
+ $function = array( $function, '' );
+ } else {
+ $function = (array) $function;
+ }
+
+ if (is_object($function[0]) ) {
+ return spl_object_hash($function[0]) . $function[1];
+ } else if ( is_string($function[0]) ) {
+ // Static Calling
+ return $function[0].$function[1];
+ }
+}
+
+function _delete_all_posts() {
+ global $wpdb;
+
+ $all_posts = $wpdb->get_col("SELECT ID from {$wpdb->posts}");
+ if ($all_posts) {
+ foreach ($all_posts as $id)
+ wp_delete_post( $id, true );
+ }
+}
+
diff --git a/tests/phpunit/includes/install.php b/tests/phpunit/includes/install.php
new file mode 100644
index 0000000000..63de9d2185
--- /dev/null
+++ b/tests/phpunit/includes/install.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ * Installs WordPress for the purpose of the unit-tests
+ *
+ * @todo Reuse the init/load code in init.php
+ */
+error_reporting( E_ALL & ~E_DEPRECATED & ~E_STRICT );
+
+$config_file_path = $argv[1];
+$multisite = ! empty( $argv[2] );
+
+define( 'WP_INSTALLING', true );
+require_once $config_file_path;
+require_once dirname( __FILE__ ) . '/functions.php';
+
+$_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1';
+$_SERVER['HTTP_HOST'] = WP_TESTS_DOMAIN;
+$PHP_SELF = $GLOBALS['PHP_SELF'] = $_SERVER['PHP_SELF'] = '/index.php';
+
+require_once ABSPATH . '/wp-settings.php';
+
+require_once ABSPATH . '/wp-admin/includes/upgrade.php';
+require_once ABSPATH . '/wp-includes/wp-db.php';
+
+define( 'WP_TESTS_VERSION_FILE', ABSPATH . '.wp-tests-version' );
+
+$wpdb->suppress_errors();
+$installed = $wpdb->get_var( "SELECT option_value FROM $wpdb->options WHERE option_name = 'siteurl'" );
+$wpdb->suppress_errors( false );
+
+$hash = get_option( 'db_version' ) . ' ' . (int) $multisite . ' ' . sha1_file( $config_file_path );
+
+if ( $installed && file_exists( WP_TESTS_VERSION_FILE ) && file_get_contents( WP_TESTS_VERSION_FILE ) == $hash )
+ return;
+
+$wpdb->query( 'SET storage_engine = INNODB' );
+$wpdb->select( DB_NAME, $wpdb->dbh );
+
+echo "Installing..." . PHP_EOL;
+
+foreach ( $wpdb->tables() as $table => $prefixed_table ) {
+ $wpdb->query( "DROP TABLE IF EXISTS $prefixed_table" );
+}
+
+foreach ( $wpdb->tables( 'ms_global' ) as $table => $prefixed_table ) {
+ $wpdb->query( "DROP TABLE IF EXISTS $prefixed_table" );
+
+ // We need to create references to ms global tables.
+ if ( $multisite )
+ $wpdb->$table = $prefixed_table;
+}
+
+wp_install( WP_TESTS_TITLE, 'admin', WP_TESTS_EMAIL, true, null, 'password' );
+
+if ( $multisite ) {
+ echo "Installing network..." . PHP_EOL;
+
+ define( 'WP_INSTALLING_NETWORK', true );
+
+ $title = WP_TESTS_TITLE . ' Network';
+ $subdomain_install = false;
+
+ install_network();
+ populate_network( 1, WP_TESTS_DOMAIN, WP_TESTS_EMAIL, $title, '/', $subdomain_install );
+}
+
+file_put_contents( WP_TESTS_VERSION_FILE, $hash );
diff --git a/tests/phpunit/includes/mock-fs.php b/tests/phpunit/includes/mock-fs.php
new file mode 100644
index 0000000000..3a99f8ba97
--- /dev/null
+++ b/tests/phpunit/includes/mock-fs.php
@@ -0,0 +1,226 @@
+<?php
+class WP_Filesystem_MockFS extends WP_Filesystem_Base {
+ private $cwd;
+
+ // Holds a array of objects which contain an array of objects, etc.
+ private $fs = null;
+
+ // Holds a array of /path/to/file.php and /path/to/dir/ map to an object in $fs above
+ // a fast more efficient way of determining if a path exists, and access to that node
+ private $fs_map = array();
+
+ public $verbose = false; // Enable to debug WP_Filesystem_Base::find_folder() / etc.
+ public $errors = array();
+ public $method = 'MockFS';
+
+ function __construct() {}
+
+ function connect() {
+ return true;
+ }
+
+ // Copy of core's function, but accepts a path.
+ function abspath( $path = false ) {
+ if ( ! $path )
+ $path = ABSPATH;
+ $folder = $this->find_folder( $path );
+
+ // Perhaps the FTP folder is rooted at the WordPress install, Check for wp-includes folder in root, Could have some false positives, but rare.
+ if ( ! $folder && $this->is_dir('/wp-includes') )
+ $folder = '/';
+ return $folder;
+ }
+
+ // Mock FS specific functions:
+
+ /**
+ * Sets initial filesystem environment and/or clears the current environment.
+ * Can also be passed the initial filesystem to be setup which is passed to self::setfs()
+ */
+ function init( $paths = '', $home_dir = '/' ) {
+ $this->fs = new MockFS_Directory_Node( '/' );
+ $this->fs_map = array(
+ '/' => $this->fs,
+ );
+ $this->cache = array(); // Used by find_folder() and friends
+ $this->cwd = isset( $this->fs_map[ $home_dir ] ) ? $this->fs_map[ $home_dir ] : '/';
+ $this->setfs( $paths );
+ }
+
+ /**
+ * "Bulk Loads" a filesystem into the internal virtual filesystem
+ */
+ function setfs( $paths ) {
+ if ( ! is_array($paths) )
+ $paths = explode( "\n", $paths );
+
+ $paths = array_filter( array_map( 'trim', $paths ) );
+
+ foreach ( $paths as $path ) {
+ // Allow for comments
+ if ( '#' == $path[0] )
+ continue;
+
+ // Directories
+ if ( '/' == $path[ strlen($path) -1 ] )
+ $this->mkdir( $path );
+ else // Files (with dummy content for now)
+ $this->put_contents( $path, 'This is a test file' );
+ }
+
+ }
+
+ /**
+ * Locates a filesystem "node"
+ */
+ private function locate_node( $path ) {
+ return isset( $this->fs_map[ $path ] ) ? $this->fs_map[ $path ] : false;
+ }
+
+ /**
+ * Locates a filesystem node for the parent of the given item
+ */
+ private function locate_parent_node( $path ) {
+ return $this->locate_node( trailingslashit( dirname( $path ) ) );
+ }
+
+ // Here starteth the WP_Filesystem functions.
+
+ function mkdir( $path, /* Optional args are ignored */ $chmod = false, $chown = false, $chgrp = false ) {
+ $path = trailingslashit( $path );
+
+ $parent_node = $this->locate_parent_node( $path );
+ if ( ! $parent_node ) {
+ $this->mkdir( dirname( $path ) );
+ $parent_node = $this->locate_parent_node( $path );
+ if ( ! $parent_node )
+ return false;
+ }
+
+ $node = new MockFS_Directory_Node( $path );
+
+ $parent_node->children[ $node->name ] = $node;
+ $this->fs_map[ $path ] = $node;
+
+ return true;
+ }
+
+ function put_contents( $path, $contents = '', $mode = null ) {
+ if ( ! $this->is_dir( dirname( $path ) ) )
+ $this->mkdir( dirname( $path ) );
+
+ $parent = $this->locate_parent_node( $path );
+ $new_file = new MockFS_File_Node( $path, $contents );
+
+ $parent->children[ $new_file->name ] = $new_file;
+ $this->fs_map[ $path ] = $new_file;
+ }
+
+ function get_contents( $file ) {
+ if ( ! $this->is_file( $file ) )
+ return false;
+ return $this->fs_map[ $file ]->contents;
+ }
+
+ function cwd() {
+ return $this->cwd->path;
+ }
+
+ function chdir( $path ) {
+ if ( ! isset( $this->fs_map[ $path ] ) )
+ return false;
+
+ $this->cwd = $this->fs_map[ $path ];
+ return true;
+ }
+
+ function exists( $path ) {
+ return isset( $this->fs_map[ $path ] ) || isset( $this->fs_map[ trailingslashit( $path ) ] );
+ }
+
+ function is_file( $file ) {
+ return isset( $this->fs_map[ $file ] ) && $this->fs_map[ $file ]->is_file();
+ }
+
+ function is_dir( $path ) {
+ $path = trailingslashit( $path );
+
+ return isset( $this->fs_map[ $path ] ) && $this->fs_map[ $path ]->is_dir();
+ }
+
+ function dirlist( $path = '.', $include_hidden = true, $recursive = false ) {
+
+ if ( empty( $path ) || '.' == $path )
+ $path = $this->cwd();
+
+ if ( ! $this->exists( $path ) )
+ return false;
+
+ $limit_file = false;
+ if ( $this->is_file( $path ) ) {
+ $limit_file = $this->locate_node( $path )->name;
+ $path = dirname( $path ) . '/';
+ }
+
+ $ret = array();
+ foreach ( $this->fs_map[ $path ]->children as $entry ) {
+ if ( '.' == $entry->name || '..' == $entry->name )
+ continue;
+
+ if ( ! $include_hidden && '.' == $entry->name )
+ continue;
+
+ if ( $limit_file && $entry->name != $limit_file )
+ continue;
+
+ $struc = array();
+ $struc['name'] = $entry->name;
+ $struc['type'] = $entry->type;
+
+ if ( 'd' == $struc['type'] ) {
+ if ( $recursive )
+ $struc['files'] = $this->dirlist( trailingslashit( $path ) . trailingslashit( $struc['name'] ), $include_hidden, $recursive );
+ else
+ $struc['files'] = array();
+ }
+
+ $ret[ $entry->name ] = $struc;
+ }
+ return $ret;
+ }
+
+}
+
+class MockFS_Node {
+ public $name; // The "name" of the entry, does not include a slash (exception, root)
+ public $type; // The type of the entry 'f' for file, 'd' for Directory
+ public $path; // The full path to the entry.
+
+ function __construct( $path ) {
+ $this->path = $path;
+ $this->name = basename( $path );
+ }
+
+ function is_file() {
+ return $this->type == 'f';
+ }
+
+ function is_dir() {
+ return $this->type == 'd';
+ }
+}
+
+class MockFS_Directory_Node extends MockFS_Node {
+ public $type = 'd';
+ public $children = array(); // The child nodes of this directory
+}
+
+class MockFS_File_Node extends MockFS_Node {
+ public $type = 'f';
+ public $contents = ''; // The contents of the file
+
+ function __construct( $path, $contents = '' ) {
+ parent::__construct( $path );
+ $this->contents = $contents;
+ }
+} \ No newline at end of file
diff --git a/tests/phpunit/includes/mock-image-editor.php b/tests/phpunit/includes/mock-image-editor.php
new file mode 100644
index 0000000000..2d8164e5c9
--- /dev/null
+++ b/tests/phpunit/includes/mock-image-editor.php
@@ -0,0 +1,43 @@
+<?php
+
+if (class_exists( 'WP_Image_Editor' ) ) :
+
+ class WP_Image_Editor_Mock extends WP_Image_Editor {
+
+ public static $load_return = true;
+ public static $test_return = true;
+ public static $save_return = array();
+
+ public function load() {
+ return self::$load_return;
+ }
+ public static function test() {
+ return self::$test_return;
+ }
+ public static function supports_mime_type( $mime_type ) {
+ return true;
+ }
+ public function resize( $max_w, $max_h, $crop = false ) {
+
+ }
+ public function multi_resize( $sizes ) {
+
+ }
+ public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) {
+
+ }
+ public function rotate( $angle ) {
+
+ }
+ public function flip( $horz, $vert ) {
+
+ }
+ public function save( $destfilename = null, $mime_type = null ) {
+ return self::$save_return;
+ }
+ public function stream( $mime_type = null ) {
+
+ }
+ }
+
+endif;
diff --git a/tests/phpunit/includes/mock-mailer.php b/tests/phpunit/includes/mock-mailer.php
new file mode 100644
index 0000000000..f52a95a064
--- /dev/null
+++ b/tests/phpunit/includes/mock-mailer.php
@@ -0,0 +1,26 @@
+<?php
+require_once( ABSPATH . '/wp-includes/class-phpmailer.php' );
+
+class MockPHPMailer extends PHPMailer {
+ var $mock_sent = array();
+
+ // override the Send function so it doesn't actually send anything
+ function Send() {
+ try {
+ if ( ! $this->PreSend() )
+ return false;
+
+ $this->mock_sent[] = array(
+ 'to' => $this->to,
+ 'cc' => $this->cc,
+ 'bcc' => $this->bcc,
+ 'header' => $this->MIMEHeader,
+ 'body' => $this->MIMEBody,
+ );
+
+ return true;
+ } catch ( phpmailerException $e ) {
+ return false;
+ }
+ }
+}
diff --git a/tests/phpunit/includes/testcase-ajax.php b/tests/phpunit/includes/testcase-ajax.php
new file mode 100644
index 0000000000..fcceacc42b
--- /dev/null
+++ b/tests/phpunit/includes/testcase-ajax.php
@@ -0,0 +1,182 @@
+<?php
+/**
+ * Ajax test cases
+ *
+ * @package WordPress
+ * @subpackage UnitTests
+ * @since 3.4.0
+ */
+
+/**
+ * Ajax test case class
+ *
+ * @package WordPress
+ * @subpackage UnitTests
+ * @since 3.4.0
+ */
+abstract class WP_Ajax_UnitTestCase extends WP_UnitTestCase {
+
+ /**
+ * Last AJAX response. This is set via echo -or- wp_die.
+ * @var type
+ */
+ protected $_last_response = '';
+
+ /**
+ * List of ajax actions called via POST
+ * @var type
+ */
+ protected $_core_actions_get = array( 'fetch-list', 'ajax-tag-search', 'wp-compression-test', 'imgedit-preview', 'oembed_cache' );
+
+ /**
+ * Saved error reporting level
+ * @var int
+ */
+ protected $_error_level = 0;
+
+ /**
+ * List of ajax actions called via GET
+ * @var type
+ */
+ protected $_core_actions_post = array(
+ 'oembed_cache', 'image-editor', 'delete-comment', 'delete-tag', 'delete-link',
+ 'delete-meta', 'delete-post', 'trash-post', 'untrash-post', 'delete-page', 'dim-comment',
+ 'add-link-category', 'add-tag', 'get-tagcloud', 'get-comments', 'replyto-comment',
+ 'edit-comment', 'add-menu-item', 'add-meta', 'add-user', 'autosave', 'closed-postboxes',
+ 'hidden-columns', 'update-welcome-panel', 'menu-get-metabox', 'wp-link-ajax',
+ 'menu-locations-save', 'menu-quick-search', 'meta-box-order', 'get-permalink',
+ 'sample-permalink', 'inline-save', 'inline-save-tax', 'find_posts', 'widgets-order',
+ 'save-widget', 'set-post-thumbnail', 'date_format', 'time_format', 'wp-fullscreen-save-post',
+ 'wp-remove-post-lock', 'dismiss-wp-pointer', 'nopriv_autosave'
+ );
+
+ /**
+ * Set up the test fixture.
+ * Override wp_die(), pretend to be ajax, and suppres E_WARNINGs
+ */
+ public function setUp() {
+ parent::setUp();
+
+ // Register the core actions
+ foreach ( array_merge( $this->_core_actions_get, $this->_core_actions_post ) as $action )
+ if ( function_exists( 'wp_ajax_' . str_replace( '-', '_', $action ) ) )
+ add_action( 'wp_ajax_' . $action, 'wp_ajax_' . str_replace( '-', '_', $action ), 1 );
+
+ add_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ), 1, 1 );
+ if ( !defined( 'DOING_AJAX' ) )
+ define( 'DOING_AJAX', true );
+ set_current_screen( 'ajax' );
+
+ // Clear logout cookies
+ add_action( 'clear_auth_cookie', array( $this, 'logout' ) );
+
+ // Suppress warnings from "Cannot modify header information - headers already sent by"
+ $this->_error_level = error_reporting();
+ error_reporting( $this->_error_level & ~E_WARNING );
+
+ // Make some posts
+ $this->factory->post->create_many( 5 );
+ }
+
+ /**
+ * Tear down the test fixture.
+ * Reset $_POST, remove the wp_die() override, restore error reporting
+ */
+ public function tearDown() {
+ parent::tearDown();
+ $_POST = array();
+ $_GET = array();
+ unset( $GLOBALS['post'] );
+ unset( $GLOBALS['comment'] );
+ remove_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ), 1, 1 );
+ remove_action( 'clear_auth_cookie', array( $this, 'logout' ) );
+ error_reporting( $this->_error_level );
+ set_current_screen( 'front' );
+ }
+
+ /**
+ * Clear login cookies, unset the current user
+ */
+ public function logout() {
+ unset( $GLOBALS['current_user'] );
+ $cookies = array(AUTH_COOKIE, SECURE_AUTH_COOKIE, LOGGED_IN_COOKIE, USER_COOKIE, PASS_COOKIE);
+ foreach ( $cookies as $c )
+ unset( $_COOKIE[$c] );
+ }
+
+ /**
+ * Return our callback handler
+ * @return callback
+ */
+ public function getDieHandler() {
+ return array( $this, 'dieHandler' );
+ }
+
+ /**
+ * Handler for wp_die()
+ * Save the output for analysis, stop execution by throwing an exception.
+ * Error conditions (no output, just die) will throw <code>WPAjaxDieStopException( $message )</code>
+ * You can test for this with:
+ * <code>
+ * $this->setExpectedException( 'WPAjaxDieStopException', 'something contained in $message' );
+ * </code>
+ * Normal program termination (wp_die called at then end of output) will throw <code>WPAjaxDieContinueException( $message )</code>
+ * You can test for this with:
+ * <code>
+ * $this->setExpectedException( 'WPAjaxDieContinueException', 'something contained in $message' );
+ * </code>
+ * @param string $message
+ */
+ public function dieHandler( $message ) {
+ $this->_last_response .= ob_get_clean();
+ ob_end_clean();
+ if ( '' === $this->_last_response ) {
+ if ( is_scalar( $message) ) {
+ throw new WPAjaxDieStopException( (string) $message );
+ } else {
+ throw new WPAjaxDieStopException( '0' );
+ }
+ } else {
+ throw new WPAjaxDieContinueException( $message );
+ }
+ }
+
+ /**
+ * Switch between user roles
+ * E.g. administrator, editor, author, contributor, subscriber
+ * @param string $role
+ */
+ protected function _setRole( $role ) {
+ $post = $_POST;
+ $user_id = $this->factory->user->create( array( 'role' => $role ) );
+ wp_set_current_user( $user_id );
+ $_POST = array_merge($_POST, $post);
+ }
+
+ /**
+ * Mimic the ajax handling of admin-ajax.php
+ * Capture the output via output buffering, and if there is any, store
+ * it in $this->_last_message.
+ * @param string $action
+ */
+ protected function _handleAjax($action) {
+
+ // Start output buffering
+ ini_set( 'implicit_flush', false );
+ ob_start();
+
+ // Build the request
+ $_POST['action'] = $action;
+ $_GET['action'] = $action;
+ $_REQUEST = array_merge( $_POST, $_GET );
+
+ // Call the hooks
+ do_action( 'admin_init' );
+ do_action( 'wp_ajax_' . $_REQUEST['action'], null );
+
+ // Save the output
+ $buffer = ob_get_clean();
+ if ( !empty( $buffer ) )
+ $this->_last_response = $buffer;
+ }
+}
diff --git a/tests/phpunit/includes/testcase-xmlrpc.php b/tests/phpunit/includes/testcase-xmlrpc.php
new file mode 100644
index 0000000000..e82c5e134e
--- /dev/null
+++ b/tests/phpunit/includes/testcase-xmlrpc.php
@@ -0,0 +1,30 @@
+<?php
+include_once(ABSPATH . 'wp-admin/includes/admin.php');
+include_once(ABSPATH . WPINC . '/class-IXR.php');
+include_once(ABSPATH . WPINC . '/class-wp-xmlrpc-server.php');
+
+class WP_XMLRPC_UnitTestCase extends WP_UnitTestCase {
+ protected $myxmlrpcserver;
+
+ function setUp() {
+ parent::setUp();
+
+ add_filter( 'pre_option_enable_xmlrpc', '__return_true' );
+
+ $this->myxmlrpcserver = new wp_xmlrpc_server();
+ }
+
+ function tearDown() {
+ remove_filter( 'pre_option_enable_xmlrpc', '__return_true' );
+
+ parent::tearDown();
+ }
+
+ protected function make_user_by_role( $role ) {
+ return $this->factory->user->create( array(
+ 'user_login' => $role,
+ 'user_pass' => $role,
+ 'role' => $role
+ ));
+ }
+}
diff --git a/tests/phpunit/includes/testcase.php b/tests/phpunit/includes/testcase.php
new file mode 100644
index 0000000000..c3fb4373df
--- /dev/null
+++ b/tests/phpunit/includes/testcase.php
@@ -0,0 +1,227 @@
+<?php
+
+require_once dirname( __FILE__ ) . '/factory.php';
+require_once dirname( __FILE__ ) . '/trac.php';
+
+class WP_UnitTestCase extends PHPUnit_Framework_TestCase {
+
+ protected static $forced_tickets = array();
+
+ /**
+ * @var WP_UnitTest_Factory
+ */
+ protected $factory;
+
+ function setUp() {
+ set_time_limit(0);
+
+ global $wpdb;
+ $wpdb->suppress_errors = false;
+ $wpdb->show_errors = true;
+ $wpdb->db_connect();
+ ini_set('display_errors', 1 );
+ $this->factory = new WP_UnitTest_Factory;
+ $this->clean_up_global_scope();
+ $this->start_transaction();
+ add_filter( 'wp_die_handler', array( $this, 'get_wp_die_handler' ) );
+ }
+
+ function tearDown() {
+ global $wpdb;
+ $wpdb->query( 'ROLLBACK' );
+ remove_filter( 'dbdelta_create_queries', array( $this, '_create_temporary_tables' ) );
+ remove_filter( 'query', array( $this, '_drop_temporary_tables' ) );
+ remove_filter( 'wp_die_handler', array( $this, 'get_wp_die_handler' ) );
+ }
+
+ function clean_up_global_scope() {
+ $_GET = array();
+ $_POST = array();
+ $this->flush_cache();
+ }
+
+ function flush_cache() {
+ global $wp_object_cache;
+ $wp_object_cache->group_ops = array();
+ $wp_object_cache->stats = array();
+ $wp_object_cache->memcache_debug = array();
+ $wp_object_cache->cache = array();
+ if ( method_exists( $wp_object_cache, '__remoteset' ) ) {
+ $wp_object_cache->__remoteset();
+ }
+ wp_cache_flush();
+ wp_cache_add_global_groups( array( 'users', 'userlogins', 'usermeta', 'user_meta', 'site-transient', 'site-options', 'site-lookup', 'blog-lookup', 'blog-details', 'rss', 'global-posts', 'blog-id-cache' ) );
+ wp_cache_add_non_persistent_groups( array( 'comment', 'counts', 'plugins' ) );
+ }
+
+ function start_transaction() {
+ global $wpdb;
+ $wpdb->query( 'SET autocommit = 0;' );
+ $wpdb->query( 'START TRANSACTION;' );
+ add_filter( 'dbdelta_create_queries', array( $this, '_create_temporary_tables' ) );
+ add_filter( 'query', array( $this, '_drop_temporary_tables' ) );
+ }
+
+ function _create_temporary_tables( $queries ) {
+ return str_replace( 'CREATE TABLE', 'CREATE TEMPORARY TABLE', $queries );
+ }
+
+ function _drop_temporary_tables( $query ) {
+ if ( 'DROP TABLE' === substr( $query, 0, 10 ) )
+ return 'DROP TEMPORARY TABLE ' . substr( $query, 10 );
+ return $query;
+ }
+
+ function get_wp_die_handler( $handler ) {
+ return array( $this, 'wp_die_handler' );
+ }
+
+ function wp_die_handler( $message ) {
+ throw new WPDieException( $message );
+ }
+
+ function assertWPError( $actual, $message = '' ) {
+ $this->assertInstanceOf( 'WP_Error', $actual, $message );
+ }
+
+ function assertEqualFields( $object, $fields ) {
+ foreach( $fields as $field_name => $field_value ) {
+ if ( $object->$field_name != $field_value ) {
+ $this->fail();
+ }
+ }
+ }
+
+ function assertDiscardWhitespace( $expected, $actual ) {
+ $this->assertEquals( preg_replace( '/\s*/', '', $expected ), preg_replace( '/\s*/', '', $actual ) );
+ }
+
+ function assertEqualSets( $expected, $actual ) {
+ $this->assertEquals( array(), array_diff( $expected, $actual ) );
+ $this->assertEquals( array(), array_diff( $actual, $expected ) );
+ }
+
+ function go_to( $url ) {
+ // note: the WP and WP_Query classes like to silently fetch parameters
+ // from all over the place (globals, GET, etc), which makes it tricky
+ // to run them more than once without very carefully clearing everything
+ $_GET = $_POST = array();
+ foreach (array('query_string', 'id', 'postdata', 'authordata', 'day', 'currentmonth', 'page', 'pages', 'multipage', 'more', 'numpages', 'pagenow') as $v) {
+ if ( isset( $GLOBALS[$v] ) ) unset( $GLOBALS[$v] );
+ }
+ $parts = parse_url($url);
+ if (isset($parts['scheme'])) {
+ $req = $parts['path'];
+ if (isset($parts['query'])) {
+ $req .= '?' . $parts['query'];
+ // parse the url query vars into $_GET
+ parse_str($parts['query'], $_GET);
+ }
+ } else {
+ $req = $url;
+ }
+ if ( ! isset( $parts['query'] ) ) {
+ $parts['query'] = '';
+ }
+
+ $_SERVER['REQUEST_URI'] = $req;
+ unset($_SERVER['PATH_INFO']);
+
+ $this->flush_cache();
+ unset($GLOBALS['wp_query'], $GLOBALS['wp_the_query']);
+ $GLOBALS['wp_the_query'] = new WP_Query();
+ $GLOBALS['wp_query'] = $GLOBALS['wp_the_query'];
+ $GLOBALS['wp'] = new WP();
+
+ // clean out globals to stop them polluting wp and wp_query
+ foreach ($GLOBALS['wp']->public_query_vars as $v) {
+ unset($GLOBALS[$v]);
+ }
+ foreach ($GLOBALS['wp']->private_query_vars as $v) {
+ unset($GLOBALS[$v]);
+ }
+
+ $GLOBALS['wp']->main($parts['query']);
+ }
+
+ protected function checkRequirements() {
+ parent::checkRequirements();
+ if ( WP_TESTS_FORCE_KNOWN_BUGS )
+ return;
+ $tickets = PHPUnit_Util_Test::getTickets( get_class( $this ), $this->getName( false ) );
+ foreach ( $tickets as $ticket ) {
+ if ( is_numeric( $ticket ) ) {
+ $this->knownWPBug( $ticket );
+ } elseif ( 'UT' == substr( $ticket, 0, 2 ) ) {
+ $ticket = substr( $ticket, 2 );
+ if ( $ticket && is_numeric( $ticket ) )
+ $this->knownUTBug( $ticket );
+ } elseif ( 'Plugin' == substr( $ticket, 0, 6 ) ) {
+ $ticket = substr( $ticket, 6 );
+ if ( $ticket && is_numeric( $ticket ) )
+ $this->knownPluginBug( $ticket );
+ }
+ }
+ }
+
+ /**
+ * Skips the current test if there is an open WordPress ticket with id $ticket_id
+ */
+ function knownWPBug( $ticket_id ) {
+ if ( WP_TESTS_FORCE_KNOWN_BUGS || in_array( $ticket_id, self::$forced_tickets ) )
+ return;
+ if ( ! TracTickets::isTracTicketClosed( 'http://core.trac.wordpress.org', $ticket_id ) )
+ $this->markTestSkipped( sprintf( 'WordPress Ticket #%d is not fixed', $ticket_id ) );
+ }
+
+ /**
+ * Skips the current test if there is an open unit tests ticket with id $ticket_id
+ */
+ function knownUTBug( $ticket_id ) {
+ if ( WP_TESTS_FORCE_KNOWN_BUGS || in_array( 'UT' . $ticket_id, self::$forced_tickets ) )
+ return;
+ if ( ! TracTickets::isTracTicketClosed( 'http://unit-tests.trac.wordpress.org', $ticket_id ) )
+ $this->markTestSkipped( sprintf( 'Unit Tests Ticket #%d is not fixed', $ticket_id ) );
+ }
+
+ /**
+ * Skips the current test if there is an open plugin ticket with id $ticket_id
+ */
+ function knownPluginBug( $ticket_id ) {
+ if ( WP_TESTS_FORCE_KNOWN_BUGS || in_array( 'Plugin' . $ticket_id, self::$forced_tickets ) )
+ return;
+ if ( ! TracTickets::isTracTicketClosed( 'http://plugins.trac.wordpress.org', $ticket_id ) )
+ $this->markTestSkipped( sprintf( 'WordPress Plugin Ticket #%d is not fixed', $ticket_id ) );
+ }
+
+ public static function forceTicket( $ticket ) {
+ self::$forced_tickets[] = $ticket;
+ }
+
+ /**
+ * Define constants after including files.
+ */
+ function prepareTemplate( Text_Template $template ) {
+ $template->setVar( array( 'constants' => '' ) );
+ $template->setVar( array( 'wp_constants' => PHPUnit_Util_GlobalState::getConstantsAsString() ) );
+ parent::prepareTemplate( $template );
+ }
+
+ /**
+ * Returns the name of a temporary file
+ */
+ function temp_filename() {
+ $tmp_dir = '';
+ $dirs = array( 'TMP', 'TMPDIR', 'TEMP' );
+ foreach( $dirs as $dir )
+ if ( isset( $_ENV[$dir] ) && !empty( $_ENV[$dir] ) ) {
+ $tmp_dir = $dir;
+ break;
+ }
+ if ( empty( $tmp_dir ) ) {
+ $tmp_dir = '/tmp';
+ }
+ $tmp_dir = realpath( $dir );
+ return tempnam( $tmp_dir, 'wpunit' );
+ }
+}
diff --git a/tests/phpunit/includes/trac.php b/tests/phpunit/includes/trac.php
new file mode 100644
index 0000000000..70b56a0a13
--- /dev/null
+++ b/tests/phpunit/includes/trac.php
@@ -0,0 +1,54 @@
+<?php
+
+class TracTickets {
+ /**
+ * When open tickets for a Trac install is requested, the results are stored here.
+ *
+ * @var array
+ */
+ protected static $trac_ticket_cache = array();
+
+ /**
+ * Checks if track ticket #$ticket_id is resolved
+ *
+ * @return bool|null true if the ticket is resolved, false if not resolved, null on error
+ */
+ public static function isTracTicketClosed( $trac_url, $ticket_id ) {
+ if ( ! isset( self::$trac_ticket_cache[ $trac_url ] ) ) {
+ // In case you're running the tests offline, keep track of open tickets.
+ $file = DIR_TESTDATA . '/.trac-ticket-cache.' . str_replace( array( 'http://', 'https://', '/' ), array( '', '', '-' ), rtrim( $trac_url, '/' ) );
+ $tickets = @file_get_contents( $trac_url . '/query?status=%21closed&format=csv&col=id' );
+ // Check if our HTTP request failed.
+ if ( false === $tickets ) {
+ if ( file_exists( $file ) ) {
+ register_shutdown_function( array( 'TracTickets', 'usingLocalCache' ) );
+ $tickets = file_get_contents( $file );
+ } else {
+ register_shutdown_function( array( 'TracTickets', 'forcingKnownBugs' ) );
+ self::$trac_ticket_cache[ $trac_url ] = array();
+ return true; // Assume the ticket is closed, which means it gets run.
+ }
+ } else {
+ $tickets = substr( $tickets, 2 ); // remove 'id' column header
+ $tickets = trim( $tickets );
+ file_put_contents( $file, $tickets );
+ }
+ $tickets = explode( "\r\n", $tickets );
+ self::$trac_ticket_cache[ $trac_url ] = $tickets;
+ }
+
+ return ! in_array( $ticket_id, self::$trac_ticket_cache[ $trac_url ] );
+ }
+
+ public static function usingLocalCache() {
+ echo PHP_EOL . "\x1b[0m\x1b[30;43m\x1b[2K";
+ echo 'INFO: Trac was inaccessible, so a local ticket status cache was used.' . PHP_EOL;
+ echo "\x1b[0m\x1b[2K";
+ }
+
+ public static function forcingKnownBugs() {
+ echo PHP_EOL . "\x1b[0m\x1b[37;41m\x1b[2K";
+ echo "ERROR: Trac was inaccessible, so known bugs weren't able to be skipped." . PHP_EOL;
+ echo "\x1b[0m\x1b[2K";
+ }
+}
diff --git a/tests/phpunit/includes/utils.php b/tests/phpunit/includes/utils.php
new file mode 100644
index 0000000000..b6d722663c
--- /dev/null
+++ b/tests/phpunit/includes/utils.php
@@ -0,0 +1,365 @@
+<?php
+
+// misc help functions and utilities
+
+function rand_str($len=32) {
+ return substr(md5(uniqid(rand())), 0, $len);
+}
+
+// strip leading and trailing whitespace from each line in the string
+function strip_ws($txt) {
+ $lines = explode("\n", $txt);
+ $result = array();
+ foreach ($lines as $line)
+ if (trim($line))
+ $result[] = trim($line);
+
+ return trim(join("\n", $result));
+}
+
+// helper class for testing code that involves actions and filters
+// typical use:
+// $ma = new MockAction();
+// add_action('foo', array(&$ma, 'action'));
+class MockAction {
+ var $events;
+ var $debug;
+
+ function MockAction($debug=0) {
+ $this->reset();
+ $this->debug = $debug;
+ }
+
+ function reset() {
+ $this->events = array();
+ }
+
+ function current_filter() {
+ if (is_callable('current_filter'))
+ return current_filter();
+ global $wp_actions;
+ return end($wp_actions);
+ }
+
+ function action($arg) {
+if ($this->debug) dmp(__FUNCTION__, $this->current_filter());
+ $args = func_get_args();
+ $this->events[] = array('action' => __FUNCTION__, 'tag'=>$this->current_filter(), 'args'=>$args);
+ return $arg;
+ }
+
+ function action2($arg) {
+if ($this->debug) dmp(__FUNCTION__, $this->current_filter());
+
+ $args = func_get_args();
+ $this->events[] = array('action' => __FUNCTION__, 'tag'=>$this->current_filter(), 'args'=>$args);
+ return $arg;
+ }
+
+ function filter($arg) {
+if ($this->debug) dmp(__FUNCTION__, $this->current_filter());
+
+ $args = func_get_args();
+ $this->events[] = array('filter' => __FUNCTION__, 'tag'=>$this->current_filter(), 'args'=>$args);
+ return $arg;
+ }
+
+ function filter2($arg) {
+if ($this->debug) dmp(__FUNCTION__, $this->current_filter());
+
+ $args = func_get_args();
+ $this->events[] = array('filter' => __FUNCTION__, 'tag'=>$this->current_filter(), 'args'=>$args);
+ return $arg;
+ }
+
+ function filter_append($arg) {
+if ($this->debug) dmp(__FUNCTION__, $this->current_filter());
+
+ $args = func_get_args();
+ $this->events[] = array('filter' => __FUNCTION__, 'tag'=>$this->current_filter(), 'args'=>$args);
+ return $arg . '_append';
+ }
+
+ function filterall($tag, $arg=NULL) {
+ // this one doesn't return the result, so it's safe to use with the new 'all' filter
+if ($this->debug) dmp(__FUNCTION__, $this->current_filter());
+
+ $args = func_get_args();
+ $this->events[] = array('filter' => __FUNCTION__, 'tag'=>$tag, 'args'=>array_slice($args, 1));
+ }
+
+ // return a list of all the actions, tags and args
+ function get_events() {
+ return $this->events;
+ }
+
+ // return a count of the number of times the action was called since the last reset
+ function get_call_count($tag='') {
+ if ($tag) {
+ $count = 0;
+ foreach ($this->events as $e)
+ if ($e['action'] == $tag)
+ ++$count;
+ return $count;
+ }
+ return count($this->events);
+ }
+
+ // return an array of the tags that triggered calls to this action
+ function get_tags() {
+ $out = array();
+ foreach ($this->events as $e) {
+ $out[] = $e['tag'];
+ }
+ return $out;
+ }
+
+ // return an array of args passed in calls to this action
+ function get_args() {
+ $out = array();
+ foreach ($this->events as $e)
+ $out[] = $e['args'];
+ return $out;
+ }
+}
+
+// convert valid xml to an array tree structure
+// kinda lame but it works with a default php 4 install
+class testXMLParser {
+ var $xml;
+ var $data = array();
+
+ function testXMLParser($in) {
+ $this->xml = xml_parser_create();
+ xml_set_object($this->xml, $this);
+ xml_parser_set_option($this->xml,XML_OPTION_CASE_FOLDING, 0);
+ xml_set_element_handler($this->xml, array(&$this, 'startHandler'), array(&$this, 'endHandler'));
+ xml_set_character_data_handler($this->xml, array(&$this, 'dataHandler'));
+ $this->parse($in);
+ }
+
+ function parse($in) {
+ $parse = xml_parse($this->xml, $in, sizeof($in));
+ if (!$parse) {
+ trigger_error(sprintf("XML error: %s at line %d",
+ xml_error_string(xml_get_error_code($this->xml)),
+ xml_get_current_line_number($this->xml)), E_USER_ERROR);
+ xml_parser_free($this->xml);
+ }
+ return true;
+ }
+
+ function startHandler($parser, $name, $attributes) {
+ $data['name'] = $name;
+ if ($attributes) { $data['attributes'] = $attributes; }
+ $this->data[] = $data;
+ }
+
+ function dataHandler($parser, $data) {
+ $index = count($this->data) - 1;
+ @$this->data[$index]['content'] .= $data;
+ }
+
+ function endHandler($parser, $name) {
+ if (count($this->data) > 1) {
+ $data = array_pop($this->data);
+ $index = count($this->data) - 1;
+ $this->data[$index]['child'][] = $data;
+ }
+ }
+}
+
+function xml_to_array($in) {
+ $p = new testXMLParser($in);
+ return $p->data;
+}
+
+function xml_find($tree /*, $el1, $el2, $el3, .. */) {
+ $a = func_get_args();
+ $a = array_slice($a, 1);
+ $n = count($a);
+ $out = array();
+
+ if ($n < 1)
+ return $out;
+
+ for ($i=0; $i<count($tree); $i++) {
+# echo "checking '{$tree[$i][name]}' == '{$a[0]}'\n";
+# var_dump($tree[$i]['name'], $a[0]);
+ if ($tree[$i]['name'] == $a[0]) {
+# echo "n == {$n}\n";
+ if ($n == 1)
+ $out[] = $tree[$i];
+ else {
+ $subtree =& $tree[$i]['child'];
+ $call_args = array($subtree);
+ $call_args = array_merge($call_args, array_slice($a, 1));
+ $out = array_merge($out, call_user_func_array('xml_find', $call_args));
+ }
+ }
+ }
+
+ return $out;
+}
+
+function xml_join_atts($atts) {
+ $a = array();
+ foreach ($atts as $k=>$v)
+ $a[] = $k.'="'.$v.'"';
+ return join(' ', $a);
+}
+
+function xml_array_dumbdown(&$data) {
+ $out = array();
+
+ foreach (array_keys($data) as $i) {
+ $name = $data[$i]['name'];
+ if (!empty($data[$i]['attributes']))
+ $name .= ' '.xml_join_atts($data[$i]['attributes']);
+
+ if (!empty($data[$i]['child'])) {
+ $out[$name][] = xml_array_dumbdown($data[$i]['child']);
+ }
+ else
+ $out[$name] = $data[$i]['content'];
+ }
+
+ return $out;
+}
+
+function dmp() {
+ $args = func_get_args();
+
+ foreach ($args as $thing)
+ echo (is_scalar($thing) ? strval($thing) : var_export($thing, true)), "\n";
+}
+
+function dmp_filter($a) {
+ dmp($a);
+ return $a;
+}
+
+function get_echo($callable, $args = array()) {
+ ob_start();
+ call_user_func_array($callable, $args);
+ return ob_get_clean();
+}
+
+// recursively generate some quick assertEquals tests based on an array
+function gen_tests_array($name, $array) {
+ $out = array();
+ foreach ($array as $k=>$v) {
+ if (is_numeric($k))
+ $index = strval($k);
+ else
+ $index = "'".addcslashes($k, "\n\r\t'\\")."'";
+
+ if (is_string($v)) {
+ $out[] = '$this->assertEquals( \'' . addcslashes($v, "\n\r\t'\\") . '\', $'.$name.'['.$index.'] );';
+ }
+ elseif (is_numeric($v)) {
+ $out[] = '$this->assertEquals( ' . $v . ', $'.$name.'['.$index.'] );';
+ }
+ elseif (is_array($v)) {
+ $out[] = gen_tests_array("{$name}[{$index}]", $v);
+ }
+ }
+ return join("\n", $out)."\n";
+}
+
+/**
+ * Use to create objects by yourself
+ */
+class MockClass {};
+
+/**
+ * Drops all tables from the WordPress database
+ */
+function drop_tables() {
+ global $wpdb;
+ $tables = $wpdb->get_col('SHOW TABLES;');
+ foreach ($tables as $table)
+ $wpdb->query("DROP TABLE IF EXISTS {$table}");
+}
+
+function print_backtrace() {
+ $bt = debug_backtrace();
+ echo "Backtrace:\n";
+ $i = 0;
+ foreach ($bt as $stack) {
+ echo ++$i, ": ";
+ if ( isset($stack['class']) )
+ echo $stack['class'].'::';
+ if ( isset($stack['function']) )
+ echo $stack['function'].'() ';
+ echo "line {$stack[line]} in {$stack[file]}\n";
+ }
+ echo "\n";
+}
+
+// mask out any input fields matching the given name
+function mask_input_value($in, $name='_wpnonce') {
+ return preg_replace('@<input([^>]*) name="'.preg_quote($name).'"([^>]*) value="[^>]*" />@', '<input$1 name="'.preg_quote($name).'"$2 value="***" />', $in);
+}
+
+$GLOBALS['_wp_die_disabled'] = false;
+function _wp_die_handler( $message, $title = '', $args = array() ) {
+ if ( !$GLOBALS['_wp_die_disabled'] ) {
+ _default_wp_die_handler( $message, $title, $args );
+ } else {
+ //Ignore at our peril
+ }
+}
+
+function _disable_wp_die() {
+ $GLOBALS['_wp_die_disabled'] = true;
+}
+
+function _enable_wp_die() {
+ $GLOBALS['_wp_die_disabled'] = false;
+}
+
+function _wp_die_handler_filter() {
+ return '_wp_die_handler';
+}
+
+if ( !function_exists( 'str_getcsv' ) ) {
+ function str_getcsv( $input, $delimiter = ',', $enclosure = '"', $escape = "\\" ) {
+ $fp = fopen( 'php://temp/', 'r+' );
+ fputs( $fp, $input );
+ rewind( $fp );
+ $data = fgetcsv( $fp, strlen( $input ), $delimiter, $enclosure );
+ fclose( $fp );
+ return $data;
+ }
+}
+
+function _rmdir( $path ) {
+ if ( in_array(basename( $path ), array( '.', '..' ) ) ) {
+ return;
+ } elseif ( is_file( $path ) ) {
+ unlink( $path );
+ } elseif ( is_dir( $path ) ) {
+ foreach ( scandir( $path ) as $file )
+ _rmdir( $path . '/' . $file );
+ rmdir( $path );
+ }
+}
+
+/**
+ * Removes the post type and its taxonomy associations.
+ */
+function _unregister_post_type( $cpt_name ) {
+ unset( $GLOBALS['wp_post_types'][ $cpt_name ] );
+ unset( $GLOBALS['_wp_post_type_features'][ $cpt_name ] );
+
+ foreach ( $GLOBALS['wp_taxonomies'] as $taxonomy ) {
+ if ( false !== $key = array_search( $cpt_name, $taxonomy->object_type ) ) {
+ unset( $taxonomy->object_type[$key] );
+ }
+ }
+}
+
+function _unregister_taxonomy( $taxonomy_name ) {
+ unset( $GLOBALS['wp_taxonomies'][$taxonomy_name] );
+}
diff --git a/tests/phpunit/includes/wp-profiler.php b/tests/phpunit/includes/wp-profiler.php
new file mode 100644
index 0000000000..1ea4d2e49e
--- /dev/null
+++ b/tests/phpunit/includes/wp-profiler.php
@@ -0,0 +1,216 @@
+<?php
+
+/*
+A simple manually-instrumented profiler for WordPress.
+
+This records basic execution time, and a summary of the actions and SQL queries run within each block.
+
+start() and stop() must be called in pairs, for example:
+
+function something_to_profile() {
+ wppf_start(__FUNCTION__);
+ do_stuff();
+ wppf_stop();
+}
+
+Multiple profile blocks are permitted, and they may be nested.
+
+*/
+
+class WPProfiler {
+ var $stack;
+ var $profile;
+
+ // constructor
+ function WPProfiler() {
+ $this->stack = array();
+ $this->profile = array();
+ }
+
+ function start($name) {
+ $time = $this->microtime();
+
+ if (!$this->stack) {
+ // log all actions and filters
+ add_filter('all', array(&$this, 'log_filter'));
+ }
+
+ // reset the wpdb queries log, storing it on the profile stack if necessary
+ global $wpdb;
+ if ($this->stack) {
+ $this->stack[count($this->stack)-1]['queries'] = $wpdb->queries;
+ }
+ $wpdb->queries = array();
+
+ global $wp_object_cache;
+
+ $this->stack[] = array(
+ 'start' => $time,
+ 'name' => $name,
+ 'cache_cold_hits' => $wp_object_cache->cold_cache_hits,
+ 'cache_warm_hits' => $wp_object_cache->warm_cache_hits,
+ 'cache_misses' => $wp_object_cache->cache_misses,
+ 'cache_dirty_objects' => $this->_dirty_objects_count($wp_object_cache->dirty_objects),
+ 'actions' => array(),
+ 'filters' => array(),
+ 'queries' => array(),
+ );
+
+ }
+
+ function stop() {
+ $item = array_pop($this->stack);
+ $time = $this->microtime($item['start']);
+ $name = $item['name'];
+
+ global $wpdb;
+ $item['queries'] = $wpdb->queries;
+ global $wp_object_cache;
+
+ $cache_dirty_count = $this->_dirty_objects_count($wp_object_cache->dirty_objects);
+ $cache_dirty_delta = $this->array_sub($cache_dirty_count, $item['cache_dirty_objects']);
+
+ if (isset($this->profile[$name])) {
+ $this->profile[$name]['time'] += $time;
+ $this->profile[$name]['calls'] ++;
+ $this->profile[$name]['cache_cold_hits'] += ($wp_object_cache->cold_cache_hits - $item['cache_cold_hits']);
+ $this->profile[$name]['cache_warm_hits'] += ($wp_object_cache->warm_cache_hits - $item['cache_warm_hits']);
+ $this->profile[$name]['cache_misses'] += ($wp_object_cache->cache_misses - $item['cache_misses']);
+ $this->profile[$name]['cache_dirty_objects'] = array_add( $this->profile[$name]['cache_dirty_objects'], $cache_dirty_delta) ;
+ $this->profile[$name]['actions'] = array_add( $this->profile[$name]['actions'], $item['actions'] );
+ $this->profile[$name]['filters'] = array_add( $this->profile[$name]['filters'], $item['filters'] );
+ $this->profile[$name]['queries'] = array_add( $this->profile[$name]['queries'], $item['queries'] );
+ #$this->_query_summary($item['queries'], $this->profile[$name]['queries']);
+
+ }
+ else {
+ $queries = array();
+ $this->_query_summary($item['queries'], $queries);
+ $this->profile[$name] = array(
+ 'time' => $time,
+ 'calls' => 1,
+ 'cache_cold_hits' => ($wp_object_cache->cold_cache_hits - $item['cache_cold_hits']),
+ 'cache_warm_hits' => ($wp_object_cache->warm_cache_hits - $item['cache_warm_hits']),
+ 'cache_misses' => ($wp_object_cache->cache_misses - $item['cache_misses']),
+ 'cache_dirty_objects' => $cache_dirty_delta,
+ 'actions' => $item['actions'],
+ 'filters' => $item['filters'],
+# 'queries' => $item['queries'],
+ 'queries' => $queries,
+ );
+ }
+
+ if (!$this->stack) {
+ remove_filter('all', array(&$this, 'log_filter'));
+ }
+ }
+
+ function microtime($since = 0.0) {
+ list($usec, $sec) = explode(' ', microtime());
+ return (float)$sec + (float)$usec - $since;
+ }
+
+ function log_filter($tag) {
+ if ($this->stack) {
+ global $wp_actions;
+ if ($tag == end($wp_actions))
+ @$this->stack[count($this->stack)-1]['actions'][$tag] ++;
+ else
+ @$this->stack[count($this->stack)-1]['filters'][$tag] ++;
+ }
+ return $arg;
+ }
+
+ function log_action($tag) {
+ if ($this->stack)
+ @$this->stack[count($this->stack)-1]['actions'][$tag] ++;
+ }
+
+ function _current_action() {
+ global $wp_actions;
+ return $wp_actions[count($wp_actions)-1];
+ }
+
+ function results() {
+ return $this->profile;
+ }
+
+ function _query_summary($queries, &$out) {
+ foreach ($queries as $q) {
+ $sql = $q[0];
+ $sql = preg_replace('/(WHERE \w+ =) \d+/', '$1 x', $sql);
+ $sql = preg_replace('/(WHERE \w+ =) \'\[-\w]+\'/', '$1 \'xxx\'', $sql);
+
+ @$out[$sql] ++;
+ }
+ asort($out);
+ return;
+ }
+
+ function _query_count($queries) {
+ // this requires the savequeries patch at http://trac.wordpress.org/ticket/5218
+ $out = array();
+ foreach ($queries as $q) {
+ if (empty($q[2]))
+ @$out['unknown'] ++;
+ else
+ @$out[$q[2]] ++;
+ }
+ return $out;
+ }
+
+ function _dirty_objects_count($dirty_objects) {
+ $out = array();
+ foreach (array_keys($dirty_objects) as $group)
+ $out[$group] = count($dirty_objects[$group]);
+ return $out;
+ }
+
+ function array_add($a, $b) {
+ $out = $a;
+ foreach (array_keys($b) as $key)
+ if (array_key_exists($key, $out))
+ $out[$key] += $b[$key];
+ else
+ $out[$key] = $b[$key];
+ return $out;
+ }
+
+ function array_sub($a, $b) {
+ $out = $a;
+ foreach (array_keys($b) as $key)
+ if (array_key_exists($key, $b))
+ $out[$key] -= $b[$key];
+ return $out;
+ }
+
+ function print_summary() {
+ $results = $this->results();
+
+ printf("\nname calls time action filter warm cold misses dirty\n");
+ foreach ($results as $name=>$stats) {
+ printf("%24.24s %6d %6.4f %6d %6d %6d %6d %6d %6d\n", $name, $stats['calls'], $stats['time'], array_sum($stats['actions']), array_sum($stats['filters']), $stats['cache_warm_hits'], $stats['cache_cold_hits'], $stats['cache_misses'], array_sum($stats['cache_dirty_objects']));
+ }
+ }
+}
+
+global $wppf;
+$wppf = new WPProfiler();
+
+function wppf_start($name) {
+ $GLOBALS['wppf']->start($name);
+}
+
+function wppf_stop() {
+ $GLOBALS['wppf']->stop();
+}
+
+function wppf_results() {
+ return $GLOBALS['wppf']->results();
+}
+
+function wppf_print_summary() {
+ $GLOBALS['wppf']->print_summary();
+}
+
+?> \ No newline at end of file