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
This commit is contained in:
Andrew Nacin
2013-08-29 18:39:34 +00:00
parent d34baebc1d
commit 8045afd81b
318 changed files with 36 additions and 39 deletions

View File

@@ -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'] );

View File

@@ -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 {}

View File

@@ -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 );
}
}

View File

@@ -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 );
}
}

View File

@@ -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 );

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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
));
}
}

View File

@@ -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' );
}
}

View File

@@ -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";
}
}

View File

@@ -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] );
}

View File

@@ -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();
}
?>