├── .gitmodules ├── CONTRIBUTING.md ├── hm-dev.mail.php ├── hm-dev.debug.php ├── hm-dev.plugin.php ├── README.md ├── hm-dev.wp-cli.test.php ├── hm-dev.wp-cli.import.php └── hm-dev.phpunit.php /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "timestack"] 2 | path = timestack 3 | url = git://github.com/joehoyle/Time-Stack-Plugin.git 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contribution guidelines ## 2 | 3 | ## Workflow ## 4 | 5 | * Develop on a feature branch and send a pull request for review. 6 | * Assign the pull request to one of the following contacts: 7 | * Primary: Joe Hoyle [@joehoyle](https://github.com/joehoyle) 8 | * Secondary: 9 | 10 | ## Coding Standards ## 11 | 12 | Please follow these recommendations 13 | [http://codex.wordpress.org/WordPress_Coding_Standards](http://codex.wordpress.org/WordPress_Coding_Standards) 14 | -------------------------------------------------------------------------------- /hm-dev.mail.php: -------------------------------------------------------------------------------- 1 | 13 | 14 | 17 | 18 |
19 | 20 |
21 | 
22 | 	
'; 44 | 45 | return $code; 46 | 47 | } 48 | 49 | function hm_backtrace( $limit = 0 ) { 50 | 51 | $new = array(); 52 | $backtrace = debug_backtrace(); 53 | 54 | array_shift( $backtrace ); 55 | 56 | foreach( $backtrace as $num => $val ) { 57 | 58 | if ( $val['function'] == 'do_action' ) 59 | $new[$num] = reset( $val['args'] ) . ' (via do_action)'; 60 | 61 | else 62 | $new[$num] = array( 'function' => $val['function'] ); 63 | 64 | if ( ! empty( $val['line'] ) ) 65 | $new[$num]['line'] = $val['line']; 66 | 67 | if ( ! empty( $val['file'] ) ) 68 | $new[$num]['file'] = $val['file']; 69 | 70 | if ( !empty( $val['class'] ) ) 71 | $new[$num]['class'] = $val['class']; 72 | 73 | } 74 | 75 | hm( $new ); 76 | 77 | } 78 | 79 | /** 80 | * Intelligently error_log the passed var. 81 | * 82 | * @param mixed $code 83 | */ 84 | function hm_log( $code ) { 85 | 86 | // var_dump everything except arrays and objects 87 | if ( ! is_array( $code ) && ! is_object( $code ) ) : 88 | error_log( var_export( $code, true ) ); 89 | 90 | else : 91 | 92 | error_log( print_r( $code, true ) ); 93 | endif; 94 | 95 | return $code; 96 | } -------------------------------------------------------------------------------- /hm-dev.plugin.php: -------------------------------------------------------------------------------- 1 | and admin_bar site name with DEV ? 31 | add_filter( 'wp_title', $dev_title = function( $title ) { 32 | 33 | $mark = 'DEV • '; 34 | 35 | // Make sure it isn't added twice 36 | return $mark . str_ireplace( $mark, '', $title ); 37 | 38 | } ); 39 | 40 | add_filter( 'admin_title', $dev_title ); 41 | 42 | add_filter( 'admin_bar_menu', function() use ( $dev_title ) { 43 | 44 | global $wp_admin_bar; 45 | 46 | $wp_admin_bar->add_node( array( 47 | 'id' => 'site-name', 48 | 'title' => $dev_title( $wp_admin_bar->get_node( 'site-name' )->title ) 49 | ) ); 50 | 51 | }, 31 ); 52 | 53 | /** 54 | * Add a nice red to the admin bar when we're in development mode 55 | */ 56 | function hm_dev_colorize() { ?> 57 | 77 | ` and admin bar site name menu. 12 | 13 | ### Debug functions 14 | 15 | Our better `var_dump` / `print_r` and others. 16 | 17 | ```` 18 | hm( $foo ); 19 | hm_log( $foo ); 20 | ```` 21 | 22 | ### Safe Email 23 | 24 | All email sent using `wp_mail` is redirected to whatever is defined as `HM_DEV_EMAIL`, default is `dev@hmn.md`. 25 | 26 | You can override by the email messages are redirected to by adding the following line to your `wp-config.php`: 27 | 28 | ```` 29 | define( 'HM_DEV_EMAIL', 'email_goes_here' ); 30 | ```` 31 | 32 | If you want to disable the redirect and allow all email to go it the original recipients then add the following to your `wp-config.php` file: 33 | 34 | ```` 35 | define( 'HM_DEV_EMAIL', false ); 36 | ```` 37 | 38 | ### WP CLI `import` command 39 | 40 | Add a `import` command to wp-cli to allow easy synching of database and uploads between your local server and the production server. 41 | 42 | Knows about WP Thumb so won't import the `uploads/cache` dir. 43 | 44 | ```` 45 | $ wp import uploads --uploads_dir="2012" 46 | $ wp import db 47 | ```` 48 | 49 | To get those commands to work `define` the following in your `wp-config-local.php` file. 50 | 51 | ```` 52 | define( 'IMPORT_DB_HOST', 'database_hostname' ); 53 | define( 'IMPORT_DB_USER', 'database_user_name' ); 54 | define( 'IMPORT_DB_NAME', 'database_name' ); 55 | define( 'IMPORT_DB_PASSWORD', 'database_password' ); 56 | 57 | define( 'IMPORT_UPLOADS_SSH_HOST', 'ssh_hostname' ); 58 | define( 'IMPORT_UPLOADS_SSH_USER', 'ssh_username' ); 59 | define( 'IMPORT_UPLOADS_REMOTE_PATH', 'remote_path_to_uploads_dir' ); 60 | ```` 61 | 62 | ### WP Unit 63 | 64 | Submodules in our fork of WP Unit (originally from https://github.com/nunomorgadinho/wp-unit). 65 | 66 | UNIT TEST ALL THE THINGS 67 | 68 | WP Unit requires the PHPUnit PEAR module to be installed. 69 | 70 | ```` 71 | $ sudo pear config-set auto_discover 1 72 | $ sudo pear install pear.phpunit.de/PHPUnit 73 | ```` 74 | 75 | ### WP CLI WP Unit `test` command 76 | 77 | Adds support for running WP Unit tests using WP CLI 78 | 79 | ```` 80 | $ wp test show 81 | $ wp test run 82 | $ wp test run exampleTestCase 83 | ```` 84 | 85 | ### Timestack 86 | 87 | Submodules in the timestack plugin from https://github.com/joehoyle/Time-Stack-Plugin 88 | 89 | ## Contribution guidelines ## 90 | 91 | see https://github.com/humanmade/hm-dev/blob/master/CONTRIBUTING.md 92 | 93 | -------------------------------------------------------------------------------- /hm-dev.wp-cli.test.php: -------------------------------------------------------------------------------- 1 | test_for_phpunit() ) 24 | return; 25 | 26 | hmdev_phpunit_load_all_test_files(); 27 | 28 | $tests = hmdev_phpunit_get_all_test_cases(); 29 | 30 | WP_CLI::line( '' ); 31 | 32 | foreach( $tests as $t ) 33 | WP_CLI::line( $t ); 34 | 35 | WP_CLI::line( '' ); 36 | 37 | } 38 | 39 | public function run( $args = array(), $assoc_args = array() ) { 40 | 41 | if ( ! $this->test_for_phpunit() ) 42 | return; 43 | 44 | $test = isset( $args[0] ) ? $args[0] : null; 45 | 46 | hmdev_phpunit_load_all_test_files(); 47 | 48 | if ( empty( $test ) ) { 49 | $this->testCases = hmdev_phpunit_get_all_test_cases(); 50 | 51 | } else { 52 | 53 | if ( strpos( $test, '*' ) ) { 54 | 55 | foreach ( hmdev_phpunit_get_all_test_cases() as $_test ) 56 | if ( preg_match( '/' . str_replace( '*', '([.]*?)', $test ) . '/', $_test ) ) 57 | $this->testCases[] = $_test; 58 | 59 | } else { 60 | 61 | $this->testCases = array( $test ); 62 | 63 | } 64 | } 65 | 66 | // Run the tests and print the results 67 | $result = new PHPUnit_Framework_TestResult; 68 | 69 | list ( $result, $printer ) = $this->_run_tests( $this->testCases ); 70 | 71 | $this->_output_results( $result ); 72 | 73 | } 74 | 75 | private function _run_tests( $classes, $classname = '' ) { 76 | 77 | $suite = new PHPUnit_Framework_TestSuite(); 78 | 79 | // Turn off BackUpGlobal until https://github.com/sebastianbergmann/phpunit/issues/451 is fixed 80 | $suite->setBackupGlobals( false ); 81 | 82 | foreach ( $classes as $testcase ) 83 | if ( ! $classname or strtolower( $testcase ) === strtolower( $classname ) ) 84 | $suite->addTestSuite( $testcase ); 85 | 86 | $result = new PHPUnit_Framework_TestResult; 87 | 88 | require_once( 'PHPUnit/TextUI/ResultPrinter.php' ); 89 | 90 | $this->printer = new WPUnitCommandResultsPrinter(); 91 | $result->addListener( $this->printer ); 92 | 93 | return array( $suite->run( $result ), $this->printer ); 94 | } 95 | 96 | 97 | private function _output_results( $result ) { 98 | 99 | WP_CLI::line( '' ); 100 | 101 | WP_CLI::line( sprintf( 'Ran %d tests. %%R%d%%n Failed, %%Y%d%%n Skipped, %%G%d%%n Passed', count( $this->printer->failed_tests ) + count( $this->printer->skipped_tests ) + count( $this->printer->passed_tests ),count( $this->printer->failed_tests ), count( $this->printer->skipped_tests ), count( $this->printer->passed_tests ) ) ); 102 | 103 | WP_CLI::line( '' ); 104 | 105 | } 106 | 107 | private function test_for_phpunit() { 108 | 109 | if ( ! @require_once( 'PHPUnit/Autoload.php' ) ) { 110 | 111 | WP_CLI::line( '%RPHPUnit not found%n, you need to install PHPUnit to use the test command, see https://github.com/humanmade/hm-dev' ); 112 | 113 | WP_CLI::line( 'Attempting to auto install PHPUnit...' ); 114 | 115 | WP_CLI::launch( 'pear config-set auto_discover 1' ); 116 | WP_CLI::launch( 'sudo pear install pear.phpunit.de/PHPUnit' ); 117 | 118 | if ( file_exists( trailingslashit( substr( get_include_path(), 2 ) ) . 'PHPUnit/Autoload.php' ) ) 119 | WP_CLI::line( '%GPHPUnit was auto installed%n, You\'ll need to run the command again.' ); 120 | 121 | return false; 122 | 123 | } 124 | 125 | return true; 126 | 127 | } 128 | 129 | } 130 | if ( class_exists( 'PHPUnit_TextUI_ResultPrinter' ) ) : 131 | class WPUnitCommandResultsPrinter extends PHPUnit_TextUI_ResultPrinter implements PHPUnit_Framework_TestListener { 132 | 133 | var $failed_tests; 134 | var $skipped_tests; 135 | var $passed_tests; 136 | var $current_test_suite; 137 | 138 | public function printResult( PHPUnit_Framework_TestResult $result ) {} 139 | 140 | public function startTestSuite( PHPUnit_Framework_TestSuite $suite ) { 141 | 142 | $name = preg_replace( '(^.*::(.*?)$)', '\\1', $suite->getName() ); 143 | 144 | $this->current_test_suite = $name; 145 | 146 | WP_CLI::line( $name ); 147 | 148 | } 149 | 150 | public function addFailure( PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time ) { 151 | 152 | $name = strpos( $test->getName(), '::' ) ? $test->getName() : $this->current_test_suite . '::' . $test->getName(); 153 | $this->failed_tests[$name] = $e->toString(); 154 | 155 | } 156 | 157 | public function addError( PHPUnit_Framework_Test $test, Exception $e, $time ) { 158 | 159 | $name = strpos( $test->getName(), '::' ) ? $test->getName() : $this->current_test_suite . '::' . $test->getName(); 160 | $this->failed_tests[$name] = 'Script Error: ' . $e->getMessage() . ' [file: ' . $e->getFile() . ' line: ' . $e->getLine() . ' backtrace: ' . $e->getTraceAsString() . ']'; 161 | 162 | } 163 | 164 | public function addSkippedTest( PHPUnit_Framework_Test $test, Exception $e, $time ) { 165 | 166 | $name = strpos( $test->getName(), '::' ) ? $test->getName() : $this->current_test_suite . '::' . $test->getName(); 167 | $this->skipped_tests[$name] = 'Skipped message: ' . $e->getMessage(); 168 | 169 | 170 | } 171 | 172 | public function addIncompleteTest( PHPUnit_Framework_Test $test, Exception $e, $time ) { 173 | 174 | $name = strpos( $test->getName(), '::' ) ? $test->getName() : $this->current_test_suite . '::' . $test->getName(); 175 | $this->skipped_tests[$name] = 'Skipped message: ' . $e->getMessage(); 176 | 177 | } 178 | 179 | public function endTest( PHPUnit_Framework_Test $test, $time ) { 180 | 181 | $name = preg_replace( '(^(.*)::(.*?)$)', '\\1', $test->getName() ); 182 | $full_name = strpos( $test->getName(), '::' ) ? $test->getName() : $this->current_test_suite . '::' . $test->getName(); 183 | 184 | if ( isset( $this->failed_tests[$full_name] ) ) { 185 | $this->print_failed( $name . ' ' . $this->failed_tests[$full_name] ); 186 | 187 | } elseif( isset( $this->skipped_tests[$full_name] ) ) { 188 | $this->print_skipped( $name . ' ' . $this->skipped_tests[$full_name] ); 189 | 190 | } else { 191 | $this->print_passed( $name ); 192 | $this->passed_tests[$name] = $name; 193 | 194 | } 195 | 196 | } 197 | 198 | private function print_passed( $message ) { 199 | 200 | WP_CLI::line( '%GPassed: %n' . $message ); 201 | 202 | } 203 | 204 | private function print_failed( $message ) { 205 | 206 | WP_CLI::line( '%RFailed: %n' . $message ); 207 | 208 | } 209 | 210 | private function print_skipped( $message ) { 211 | 212 | WP_CLI::line( '%YSkipped: %n' . $message ); 213 | 214 | } 215 | 216 | } 217 | endif; 218 | 219 | // Add the command to the wp-cli 220 | WP_CLI::add_command( 'test', 'WPUnitCommand' ); 221 | 222 | endif; -------------------------------------------------------------------------------- /hm-dev.wp-cli.import.php: -------------------------------------------------------------------------------- 1 | db_help(); 22 | 23 | $defaults = array( 24 | 'host' => defined( 'IMPORT_DB_HOST' ) ? IMPORT_DB_HOST : DB_HOST, 25 | 'user' => defined( 'IMPORT_DB_USER' ) ? IMPORT_DB_USER : DB_USER, 26 | 'password' => defined( 'IMPORT_DB_PASSWORD' ) ? IMPORT_DB_PASSWORD : '', 27 | 'name' => defined( 'IMPORT_DB_NAME' ) ? IMPORT_DB_NAME : '', 28 | 'port' => '3306', 29 | 'ssh_host' => defined( 'IMPORT_DB_SSH_HOST' ) ? IMPORT_DB_SSH_HOST : '', 30 | 'ssh_user' => defined( 'IMPORT_DB_SSH_USER' ) ? IMPORT_DB_SSH_USER : '', 31 | 'table' => '' 32 | ); 33 | 34 | $args = wp_parse_args( $args, $defaults ); 35 | 36 | $start_time = time(); 37 | 38 | if ( $args['ssh_host'] ) { 39 | shell_exec( sprintf( "ssh -f -L 3308:%s:%s %s@%s sleep 600 >> logfile", $args['host'], $args['port'], $args['ssh_user'], $args['ssh_host'] ) ); 40 | $args['host'] = '127.0.0.1'; 41 | $args['port'] = '3308'; 42 | } 43 | 44 | WP_CLI::line( 'Importing database from ' . $args['host'] . '...' . ( $args['ssh_host'] ? ' via ssh tunnel: ' . $args['ssh_host'] : '' ) ); 45 | 46 | $password = $args['password'] ? '--password=' . escapeshellarg($args['password']) : ''; 47 | 48 | // TODO pipe through sed or interconnectIT's search replace script 49 | if ( defined( 'IMPORT_DB_REMOTE_ABSPATH' ) ) 50 | $sed = " | sed s," . trailingslashit( IMPORT_DB_REMOTE_ABSPATH ) . "," . ABSPATH . ",g"; 51 | else 52 | $sed = ''; 53 | 54 | if ( $args['site'] ) { 55 | $args['table'] = "wp_{$args['site']}_commentmeta wp_{$args['site']}_comments wp_{$args['site']}_links wp_{$args['site']}_options wp_{$args['site']}_postmeta wp_{$args['site']}_posts wp_{$args['site']}_term_relationships wp_{$args['site']}_term_taxonomy wp_{$args['site']}_terms"; 56 | } 57 | 58 | $exec = sprintf( 59 | 'mysqldump --verbose --host=%s --user=%s %s -P %s %s %s %s | mysql --host=%s --user=%s --password=%s %s', 60 | $args['host'], 61 | $args['user'], 62 | $password, 63 | $args['port'], 64 | $args['name'], 65 | $args['table'], 66 | $sed, 67 | DB_HOST, 68 | DB_USER, 69 | escapeshellarg(DB_PASSWORD), 70 | DB_NAME 71 | ); 72 | 73 | WP_CLI::line( 'Running: ' . $exec ); 74 | 75 | WP_CLI::launch( $exec ); 76 | 77 | WP_CLI::success( sprintf( 'Finished. Took %d seconds', time() - $start_time ) ); 78 | 79 | wp_cache_flush(); 80 | } 81 | 82 | public function uploads( $command = array(), $args = array() ) { 83 | 84 | if ( count( $command ) == 1 && reset( $command ) == 'help' ) 85 | return $this->uploads_help(); 86 | 87 | $dir = wp_upload_dir(); 88 | 89 | $defaults = array( 90 | 'ssh_host' => defined( 'IMPORT_UPLOADS_SSH_HOST' ) ? IMPORT_UPLOADS_SSH_HOST : '', 91 | 'ssh_user' => defined( 'IMPORT_UPLOADS_SSH_USER' ) ? IMPORT_UPLOADS_SSH_USER : '', 92 | 'remote_path' => defined( 'IMPORT_UPLOADS_REMOTE_PATH' ) ? IMPORT_UPLOADS_REMOTE_PATH : '', 93 | 'uploads_dir' => '', 94 | 'local_path' => $dir['basedir'] 95 | ); 96 | 97 | $args = wp_parse_args( $args, $defaults ); 98 | 99 | if ( $args['uploads_dir'] ) { 100 | 101 | $args['remote_path'] = $args['remote_path'] ? trailingslashit( $args['remote_path'] ) . trailingslashit( ltrim( $args['uploads_dir'], '/' ) ) : ''; 102 | $args['local_path'] = trailingslashit( $args['local_path'] ) . untrailingslashit( ltrim( $args['uploads_dir'], '/' ) ); 103 | 104 | } else { 105 | 106 | $args['remote_path'] = $args['remote_path'] ? trailingslashit( $args['remote_path'] ) : ''; 107 | $args['local_path'] = untrailingslashit( $args['local_path'] ); 108 | 109 | } 110 | 111 | if ( empty( $args['remote_path'] ) ) { 112 | WP_CLI::error( 'You must specify a remote path. Use --remote_path=~/foo/bar' ); 113 | return; 114 | } 115 | 116 | if ( empty( $args['ssh_host'] ) ) { 117 | WP_CLI::error( 'You must specify a ssh host. Use --ssh_host=example.com' ); 118 | return; 119 | } 120 | 121 | if ( empty( $args['ssh_user'] ) ) { 122 | WP_CLI::error( 'You must specify a ssh user. Use --ssh_user=root' ); 123 | return; 124 | } 125 | 126 | WP_CLI::line( sprintf( 'Running rsync from %s:%s to %s', $args['ssh_host'], $args['remote_path'], $args['local_path'] ) ); 127 | 128 | WP_CLI::launch( sprintf( "rsync -avz -e ssh %s@%s:%s %s --exclude 'cache' --exclude '*backup*'", $args['ssh_user'], $args['ssh_host'], $args['remote_path'], $args['local_path'] ) ); 129 | 130 | } 131 | 132 | /** 133 | * Import a specific site from wordpress multisite site 134 | * 135 | * @todo add site to wp_blogs if it doesn't exist 136 | * @todo support site "nicename" instead of ID. Somehow. 137 | */ 138 | public function site( $command, $args ) { 139 | 140 | $site = $command[0]; 141 | 142 | WP_CLI::line( 'Importing uploads' ); 143 | 144 | $this->uploads( array(), array( 'uploads_dir' => 'sites/' . $site ) ); 145 | 146 | WP_CLI::line( 'Importing site tables' ); 147 | 148 | $this->db( array(), array( 'site' => $site ) ); 149 | 150 | // check if the site is in the blogs table 151 | global $wpdb; 152 | 153 | $details = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->blogs WHERE blog_id = %d", $site ) ); 154 | 155 | if ( ! $details ) { 156 | 157 | WP_CLI::line( 'Adding site to blogs table' ); 158 | 159 | $url = $wpdb->get_var( $wpdb->prepare( "SELECT option_value FROM wp_%d_options WHERE option_name = 'siteurl'", $site ) ); 160 | $url = parse_url( $url ); 161 | $subdomain = reset( explode( '.', $url['host'] ) ); 162 | 163 | $row = $wpdb->get_row( "SELECT * FROM $wpdb->blogs WHERE blog_id = 1" ); 164 | $row->blog_id = $site; 165 | $row->domain = $subdomain . '.' . $row->domain; 166 | 167 | $wpdb->insert( $wpdb->blogs, (array) $row ); 168 | } 169 | 170 | } 171 | 172 | /** 173 | * Help function for this command 174 | */ 175 | public function help() { 176 | WP_CLI::line( <<suppress_errors = false; 14 | $wpdb->show_errors = true; 15 | $wpdb->db_connect(); 16 | ini_set('display_errors', 1 ); 17 | 18 | $this->start_transaction(); 19 | add_filter( 'gp_get_option_uri', array( $this, 'url_filter') ); 20 | $this->activate_tested_plugin(); 21 | } 22 | 23 | function activate_tested_plugin() { 24 | if ( !$this->plugin_slug ) { 25 | return; 26 | } 27 | require_once ABSPATH . '/wp-admin/includes/plugin.php'; 28 | if ( file_exists( WP_PLUGIN_DIR . '/' . $this->plugin_slug . '.php' ) ) 29 | activate_plugin( $this->plugin_slug . '.php' ); 30 | elseif ( file_exists( WP_PLUGIN_DIR . '/' . $this->plugin_slug . '/' . $this->plugin_slug . '.php' ) ) 31 | activate_plugin( $this->plugin_slug . '/' . $this->plugin_slug . '.php' ); 32 | else 33 | throw new WP_Tests_Exception( "Couldn't find a plugin with slug $this->plugin_slug" ); 34 | } 35 | 36 | function url_filter( $url ) { 37 | return $this->url; 38 | } 39 | 40 | function _tearDown() { 41 | global $wpdb; 42 | $wpdb->query( 'ROLLBACK' ); 43 | remove_filter( 'gp_get_option_uri', array( $this, 'url_filter') ); 44 | } 45 | 46 | function clean_up_global_scope() { 47 | wp_cache_flush(); 48 | $_GET = array(); 49 | $_POST = array(); 50 | } 51 | 52 | function start_transaction() { 53 | global $wpdb; 54 | $wpdb->query( 'SET autocommit = 0;' ); 55 | $wpdb->query( 'SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;' ); 56 | $wpdb->query( 'START TRANSACTION;' ); 57 | } 58 | 59 | function force_innodb( $schema ) { 60 | foreach( $schema as &$sql ) { 61 | $sql = str_replace( ');', ') TYPE=InnoDB;', $sql ); 62 | } 63 | return $schema; 64 | } 65 | 66 | function assertWPError( $actual, $message = '' ) { 67 | $this->assertTrue( is_wp_error( $actual ), $message ); 68 | } 69 | 70 | function assertNotWPError( $actual, $message = '' ) { 71 | $this->assertNotInstanceOf( 'WP_Error', $actual, $message ); 72 | } 73 | 74 | function assertEqualFields( $object, $fields ) { 75 | foreach( $fields as $field_name => $field_value ) { 76 | if ( $object->$field_name != $field_value ) { 77 | $this->fail(); 78 | } 79 | } 80 | } 81 | 82 | /** 83 | * Assert that a zip archive contains the array 84 | * of filenames 85 | * 86 | * @access public 87 | * @param string path to zip file 88 | * @param array of filenames to check for 89 | * @return null 90 | */ 91 | function assertArchiveContains( $zip_file, $filepaths, $root = ABSPATH ) { 92 | 93 | $extracted = $this->pclzip_extract_as_string( $zip_file ); 94 | 95 | $files = array(); 96 | 97 | foreach( $filepaths as $filepath ) 98 | $filenames[] = str_ireplace( trailingslashit( $root ), '', $filepath ); 99 | 100 | foreach( $extracted as $fileInfo ) 101 | $files[] = untrailingslashit( $fileInfo['filename'] ); 102 | 103 | foreach( $filenames as $filename ) 104 | $this->assertContains( untrailingslashit( $filename ), $files ); 105 | 106 | } 107 | 108 | /** 109 | * Assert that a zip archive doesn't contain any of the files 110 | * in the array of filenames 111 | * 112 | * @access public 113 | * @param string path to zip file 114 | * @param array of filenames to check for 115 | * @return null 116 | */ 117 | function assertArchiveNotContains( $zip_file, $filenames ) { 118 | 119 | $extracted = $this->pclzip_extract_as_string( $zip_file ); 120 | 121 | $files = array(); 122 | 123 | foreach( $extracted as $fileInfo ) 124 | $files[] = $fileInfo['filename']; 125 | 126 | foreach( $filenames as $filename ) 127 | $this->assertNotContains( $filename, $files ); 128 | 129 | } 130 | 131 | /** 132 | * Assert that a zip archive contains the 133 | * correct number of files 134 | * 135 | * @access public 136 | * @param string path to zip file 137 | * @param int the number of files the archive should contain 138 | * @return null 139 | */ 140 | function assertArchiveFileCount( $zip_file, $file_count ) { 141 | 142 | $extracted = $this->pclzip_extract_as_string( $zip_file ); 143 | 144 | $this->assertEquals( count( array_filter( (array) $extracted ) ), $file_count ); 145 | 146 | } 147 | 148 | function assertURL( $url, $message = '' ) { 149 | 150 | $this->assertStringStartsWith( 'http', $url, $message ); 151 | 152 | } 153 | 154 | function assertURLReponseCode( $url, $code, $message = '' ) { 155 | 156 | $r = wp_remote_request( $url, array( 'timeout' => 30 ) ); 157 | $r_code = wp_remote_retrieve_response_code( $r ); 158 | $this->assertEquals( $code, $r_code ); 159 | } 160 | 161 | function assertURLContains( $url, $pattern, $message = '' ) { 162 | 163 | $r = wp_remote_request( $url, array( 'timeout' => 30 ) ); 164 | $body = wp_remote_retrieve_body( $r ); 165 | 166 | 167 | $this->assertContains( $pattern, $body, $message ); 168 | } 169 | 170 | function assertDiscardWhitespace( $expected, $actual ) { 171 | $this->assertEquals( preg_replace( '/\s*/', '', $expected ), preg_replace( '/\s*/', '', $actual ) ); 172 | } 173 | 174 | function assertImageRGBAtPoint( $image_path, $point, $color ) { 175 | 176 | $im = imagecreatefrompng( $image_path ); 177 | $rgb = imagecolorat($im, $point[0], $point[1]); 178 | 179 | $colors = imagecolorsforindex($im, $rgb); 180 | $colors = array( $colors['red'], $colors['green'], $colors['blue'] ); 181 | $this->assertEquals( $colors, $color ); 182 | } 183 | 184 | function assertImageAlphaAtPoint( $image_path, $point, $alpha ) { 185 | 186 | $im = imagecreatefrompng( $image_path ); 187 | $rgb = imagecolorat($im, $point[0], $point[1]); 188 | 189 | $colors = imagecolorsforindex($im, $rgb); 190 | 191 | $this->assertEquals( $colors['alpha'], $alpha ); 192 | } 193 | 194 | function checkAtLeastPHPVersion( $version ) { 195 | if ( version_compare( PHP_VERSION, $version, '<' ) ) { 196 | $this->markTestSkipped(); 197 | } 198 | } 199 | 200 | function go_to( $url ) { 201 | // note: the WP and WP_Query classes like to silently fetch parameters 202 | // from all over the place (globals, GET, etc), which makes it tricky 203 | // to run them more than once without very carefully clearing everything 204 | $_GET = $_POST = array(); 205 | $this->clean_up_global_scope(); 206 | foreach (array('query_string', 'id', 'postdata', 'authordata', 'day', 'currentmonth', 'page', 'pages', 'multipage', 'more', 'numpages', 'pagenow') as $v) { 207 | if ( isset( $GLOBALS[$v] ) ) unset( $GLOBALS[$v] ); 208 | } 209 | $parts = parse_url($url); 210 | if (isset($parts['scheme'])) { 211 | $req = $parts['path']; 212 | if (isset($parts['query'])) { 213 | $req .= '?' . $parts['query']; 214 | // parse the url query vars into $_GET 215 | parse_str($parts['query'], $_GET); 216 | } else { 217 | $parts['query'] = ''; 218 | } 219 | } 220 | else { 221 | $req = $url; 222 | } 223 | 224 | $_SERVER['REQUEST_URI'] = $req; 225 | unset($_SERVER['PATH_INFO']); 226 | 227 | wp_cache_flush(); 228 | unset($GLOBALS['wp_query'], $GLOBALS['wp_the_query']); 229 | $GLOBALS['wp_the_query'] = new WP_Query(); 230 | $GLOBALS['wp_query'] =& $GLOBALS['wp_the_query']; 231 | $GLOBALS['wp'] = new WP(); 232 | 233 | // clean out globals to stop them polluting wp and wp_query 234 | foreach ($GLOBALS['wp']->public_query_vars as $v) { 235 | unset($GLOBALS[$v]); 236 | } 237 | foreach ($GLOBALS['wp']->private_query_vars as $v) { 238 | unset($GLOBALS[$v]); 239 | } 240 | 241 | $GLOBALS['wp']->main($parts['query']); 242 | } 243 | 244 | private function pclzip_extract_as_string( $zip_file ) { 245 | 246 | require_once( ABSPATH . 'wp-admin/includes/class-pclzip.php' ); 247 | 248 | if ( ini_get( 'mbstring.func_overload' ) && function_exists( 'mb_internal_encoding' ) ) { 249 | $previous_encoding = mb_internal_encoding(); 250 | mb_internal_encoding( 'ISO-8859-1' ); 251 | } 252 | 253 | $archive = new PclZip( $zip_file ); 254 | 255 | $extracted = $archive->extract( PCLZIP_OPT_EXTRACT_AS_STRING ); 256 | 257 | if ( isset( $previous_encoding ) ) 258 | mb_internal_encoding( $previous_encoding ); 259 | 260 | return $extracted ?: array(); 261 | 262 | } 263 | 264 | } 265 | 266 | function hmdev_phpunit_load_all_test_files() { 267 | 268 | if( ! empty( $_POST['tests'] ) ) { 269 | foreach( (array) $_POST['tests'] as $test_file ) 270 | if( strpos( $test_file, '.php' ) ) { 271 | include_once( WP_PLUGIN_DIR . '/' . $test_file ); 272 | } else { 273 | $files = wptest_get_all_test_files( WP_PLUGIN_DIR . '/' . $test_file ); 274 | 275 | foreach ($files as $file) { 276 | require_once($file); 277 | } 278 | 279 | } 280 | } else { 281 | 282 | $plugins = get_plugins(); 283 | 284 | foreach( $plugins as $plugin_path => $plugin ) { 285 | if( is_plugin_active( $plugin_path ) ) { 286 | foreach( get_plugin_files( $plugin_path ) as $file ) 287 | if( strpos( $file, '/tests/' ) && end( explode( '.', $file) ) == 'php' ) 288 | include_once( WP_PLUGIN_DIR . '/' . $file ); 289 | } 290 | 291 | } 292 | 293 | if ( is_dir( get_template_directory() . '/tests/' ) ) { 294 | 295 | foreach ( glob( get_template_directory() . '/tests/*.php' ) as $file ) 296 | include_once( $file ); 297 | 298 | } 299 | 300 | if ( is_dir( WPMU_PLUGIN_DIR ) ) { 301 | 302 | foreach ( glob( WPMU_PLUGIN_DIR . '/*/tests/*.php' ) as $file ) { 303 | include_once( $file ); 304 | } 305 | 306 | } 307 | 308 | } 309 | } 310 | 311 | function hmdev_phpunit_get_all_test_cases() { 312 | 313 | $test_classes = array(); 314 | $all_classes = get_declared_classes(); 315 | // only classes that extend WPTestCase and have names that don't start with _ are included 316 | foreach ($all_classes as $class) 317 | if ($class{0} != '_' && hmdev_phpunit_test_is_descendent('PHPUnit_Framework_TestCase', $class) && $class != 'WP_UnitTestCase' ) 318 | $test_classes[] = $class; 319 | return $test_classes; 320 | } 321 | 322 | function hmdev_phpunit_test_is_descendent($parent, $class) { 323 | 324 | $ancestor = strtolower(get_parent_class($class)); 325 | 326 | while ($ancestor) { 327 | if ($ancestor == strtolower($parent)) return true; 328 | $ancestor = strtolower(get_parent_class($ancestor)); 329 | } 330 | 331 | return false; 332 | } 333 | --------------------------------------------------------------------------------