Make WordPress Core

Changeset 44701


Ignore:
Timestamp:
01/28/2019 02:10:24 PM (6 years ago)
Author:
SergeyBiryukov
Message:

Build/Test Tools: Add support for PHPUnit 7.x.

  • Create an abstract WP_UnitTestCase_Base class to share between PHPUnit 7.x and older versions.
  • Add a speed-trap loader to determine which SpeedTrapListener class needs to be loaded for the current PHPUnit version.
  • Remove unnecessary PHPUnit\Util\Test and PHPUnit_Util_Getopt inheritances.
  • Update Travis CI config to use PHPUnit 7.x for PHP 7.1, 7.2, and nightly PHP versions.

Props jipmoors, netweb, desrosj, ayeshrajans, soulseekah, SergeyBiryukov.
See #43218.

Location:
trunk
Files:
3 added
8 edited
3 copied
1 moved

Legend:

Unmodified
Added
Removed
  • trunk/.travis.yml

    r44219 r44701  
    6868  if [[ "$WP_TRAVISCI" == "travis:phpunit" ]]; then
    6969    case "$TRAVIS_PHP_VERSION" in
    70       7.3|7.2|7.1|7.0|nightly)
     70      7.3|7.2|7.1|nightly)
     71        echo "Using PHPUnit 7.x"
     72        travis_retry composer global require "phpunit/phpunit:^7"
     73        ;;
     74      7.0)
    7175        echo "Using PHPUnit 6.x"
    7276        travis_retry composer global require "phpunit/phpunit:^6"
  • trunk/phpunit.xml.dist

    r44126 r44701  
    4141    </php>
    4242    <listeners>
    43         <listener class="SpeedTrapListener" file="tests/phpunit/includes/speed-trap-listener.php">
     43        <listener class="SpeedTrapListener" file="tests/phpunit/includes/listener-loader.php">
    4444            <arguments>
    4545                <array>
  • trunk/tests/phpunit/includes/abstract-testcase.php

    r44700 r44701  
    1313 * All WordPress unit tests should inherit from this class.
    1414 */
    15 class WP_UnitTestCase extends PHPUnit_Framework_TestCase {
     15abstract class WP_UnitTestCase_Base extends PHPUnit_Framework_TestCase {
    1616
    1717    protected static $forced_tickets   = array();
     
    275275     * Saves the action and filter-related globals so they can be restored later.
    276276     *
    277      * Stores $wp_actions, $wp_current_filter, and $wp_filter
    278      * on a class variable so they can be restored on tearDown() using _restore_hooks().
     277     * Stores $wp_actions, $wp_current_filter, and $wp_filter on a class variable
     278     * so they can be restored on tearDown() using _restore_hooks().
    279279     *
    280280     * @global array $wp_actions
     
    583583            $this->assertNotEmpty( $sub_array );
    584584        }
    585     }
    586 
    587     /**
    588      * Asserts that a condition is not false.
    589      *
    590      * This method has been backported from a more recent PHPUnit version, as tests running on PHP 5.2 use
    591      * PHPUnit 3.6.x.
    592      *
    593      * @since 4.7.4
    594      *
    595      * @param bool   $condition Condition to check.
    596      * @param string $message   Optional. Message to display when the assertion fails.
    597      *
    598      * @throws PHPUnit_Framework_AssertionFailedError
    599      */
    600     public static function assertNotFalse( $condition, $message = '' ) {
    601         self::assertThat( $condition, self::logicalNot( self::isFalse() ), $message );
    602585    }
    603586
  • trunk/tests/phpunit/includes/bootstrap.php

    r44536 r44701  
    88 */
    99if ( class_exists( 'PHPUnit\Runner\Version' ) ) {
    10     require_once dirname( __FILE__ ) . '/phpunit6-compat.php';
     10    require_once dirname( __FILE__ ) . '/phpunit6/compat.php';
    1111}
    1212
     
    119119_delete_all_posts();
    120120
    121 require dirname( __FILE__ ) . '/testcase.php';
     121if ( version_compare( tests_get_phpunit_version(), '7.0', '>=' ) ) {
     122    require dirname( __FILE__ ) . '/phpunit7/testcase.php';
     123} else {
     124    require dirname( __FILE__ ) . '/testcase.php';
     125}
     126
    122127require dirname( __FILE__ ) . '/testcase-rest-api.php';
    123128require dirname( __FILE__ ) . '/testcase-rest-controller.php';
     
    145150 * how you call phpunit has no effect.
    146151 */
    147 class WP_PHPUnit_Util_Getopt extends PHPUnit_Util_Getopt {
     152class WP_PHPUnit_Util_Getopt {
    148153    protected $longOptions = array(
    149154        'exclude-group=',
    150155        'group=',
     156        'verbose=',
    151157    );
    152158    function __construct( $argv ) {
     
    158164            try {
    159165                if ( strlen( $arg ) > 1 && $arg[0] === '-' && $arg[1] === '-' ) {
    160                     PHPUnit_Util_Getopt::parseLongOption( substr( $arg, 2 ), $this->longOptions, $options, $argv );
     166                    self::parseLongOption( substr( $arg, 2 ), $this->longOptions, $options, $argv );
    161167                }
    162168            } catch ( PHPUnit_Framework_Exception $e ) {
     
    209215        }
    210216    }
     217
     218    /**
     219     * Copied from https://raw.githubusercontent.com/sebastianbergmann/phpunit/6.5.7/src/Util/Getopt.php
     220     *
     221     * @param $arg
     222     * @param $long_options
     223     * @param $opts
     224     * @param $args
     225     */
     226    protected static function parseLongOption( $arg, $long_options, &$opts, &$args ) {
     227        $count   = count( $long_options );
     228        $list    = explode( '=', $arg );
     229        $opt     = $list[0];
     230        $opt_arg = null;
     231
     232        if ( count( $list ) > 1 ) {
     233            $opt_arg = $list[1];
     234        }
     235
     236        $opt_len = strlen( $opt );
     237
     238        for ( $i = 0; $i < $count; $i++ ) {
     239            $long_opt  = $long_options[ $i ];
     240            $opt_start = substr( $long_opt, 0, $opt_len );
     241
     242            if ( $opt_start != $opt ) {
     243                continue;
     244            }
     245
     246            $opt_rest = substr( $long_opt, $opt_len );
     247
     248            if ( $opt_rest != '' && $opt[0] != '=' && $i + 1 < $count &&
     249                $opt == substr( $long_options[ $i + 1 ], 0, $opt_len ) ) {
     250                throw new Exception(
     251                    "option --$opt is ambiguous"
     252                );
     253            }
     254
     255            if ( substr( $long_opt, -1 ) == '=' ) {
     256                if ( substr( $long_opt, -2 ) != '==' ) {
     257                    if ( ! strlen( $opt_arg ) ) {
     258                        if ( false === $opt_arg = current( $args ) ) {
     259                            throw new Exception(
     260                                "option --$opt requires an argument"
     261                            );
     262                        }
     263                        next( $args );
     264                    }
     265                }
     266            } elseif ( $opt_arg ) {
     267                throw new Exception(
     268                    "option --$opt doesn't allow an argument"
     269                );
     270            }
     271
     272            $full_option = '--' . preg_replace( '/={1,2}$/', '', $long_opt );
     273            $opts[]      = array( $full_option, $opt_arg );
     274
     275            return;
     276        }
     277
     278        throw new Exception( "unrecognized option --$opt" );
     279    }
    211280}
    212281new WP_PHPUnit_Util_Getopt( $_SERVER['argv'] );
  • trunk/tests/phpunit/includes/functions.php

    r44118 r44701  
    11<?php
     2
     3/**
     4 * Retrieves PHPUnit runner version.
     5 */
     6function tests_get_phpunit_version() {
     7    if ( class_exists( 'PHPUnit_Runner_Version' ) ) {
     8        $version = PHPUnit_Runner_Version::id();
     9    } elseif ( class_exists( 'PHPUnit\Runner\Version' ) ) {
     10        // Must be parsable by PHP 5.2.x.
     11        $version = call_user_func( 'PHPUnit\Runner\Version::id' );
     12    } else {
     13        $version = 0;
     14    }
     15
     16    return $version;
     17}
    218
    319/**
  • trunk/tests/phpunit/includes/phpunit6/compat.php

    r44700 r44701  
    1616    class_alias( 'PHPUnit\Util\Getopt', 'PHPUnit_Util_Getopt' );
    1717
    18     class PHPUnit_Util_Test extends PHPUnit\Util\Test {
     18    class PHPUnit_Util_Test {
    1919
    2020        public static function getTickets( $className, $methodName ) {
    21             $annotations = self::parseTestMethodAnnotations( $className, $methodName );
     21            $annotations = PHPUnit\Util\Test::parseTestMethodAnnotations( $className, $methodName );
    2222
    2323            $tickets = array();
  • trunk/tests/phpunit/includes/phpunit7/speed-trap-listener.php

    r44700 r44701  
    5555     * @param float                   $time
    5656     */
    57     public function addError( PHPUnit_Framework_Test $test, Exception $e, $time ) {
     57    public function addError( PHPUnit\Framework\Test $test, Throwable $t, float $time ): void {
    5858    }
    5959
     
    6666     * @since Method available since Release 5.1.0
    6767     */
    68     public function addWarning( PHPUnit_Framework_Test $test, PHPUnit_Framework_Warning $e, $time ) {
     68    public function addWarning( PHPUnit\Framework\Test $test, PHPUnit\Framework\Warning $e, float $time ): void {
    6969    }
    7070
     
    7676     * @param float                                   $time
    7777     */
    78     public function addFailure( PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time ) {
     78    public function addFailure( PHPUnit\Framework\Test $test, PHPUnit\Framework\AssertionFailedError $e, float $time ): void {
    7979    }
    8080
     
    8686     * @param float                   $time
    8787     */
    88     public function addIncompleteTest( PHPUnit_Framework_Test $test, Exception $e, $time ) {
     88    public function addIncompleteTest( PHPUnit\Framework\Test $test, Throwable $t, float $time ): void {
    8989    }
    9090
     
    9797     * @since  Method available since Release 4.0.0
    9898     */
    99     public function addRiskyTest( PHPUnit_Framework_Test $test, Exception $e, $time ) {
     99    public function addRiskyTest( PHPUnit\Framework\Test $test, Throwable $t, float $time ): void {
    100100    }
    101101
     
    107107     * @param float                   $time
    108108     */
    109     public function addSkippedTest( PHPUnit_Framework_Test $test, Exception $e, $time ) {
     109    public function addSkippedTest( PHPUnit\Framework\Test $test, Throwable $t, float $time ): void {
    110110    }
    111111
     
    115115     * @param PHPUnit_Framework_Test $test
    116116     */
    117     public function startTest( PHPUnit_Framework_Test $test ) {
     117    public function startTest( PHPUnit\Framework\Test $test ): void {
    118118    }
    119119
     
    124124     * @param float                   $time
    125125     */
    126     public function endTest( PHPUnit_Framework_Test $test, $time ) {
     126    public function endTest( PHPUnit\Framework\Test $test, float $time ): void {
    127127        if ( ! $test instanceof PHPUnit_Framework_TestCase ) {
    128128            return;
     
    142142     * @param PHPUnit_Framework_TestSuite $suite
    143143     */
    144     public function startTestSuite( PHPUnit_Framework_TestSuite $suite ) {
     144    public function startTestSuite( PHPUnit\Framework\TestSuite $suite ): void {
    145145        $this->suites++;
    146146    }
     
    151151     * @param PHPUnit_Framework_TestSuite $suite
    152152     */
    153     public function endTestSuite( PHPUnit_Framework_TestSuite $suite ) {
     153    public function endTestSuite( PHPUnit\Framework\TestSuite $suite ): void {
    154154        $this->suites--;
    155155
  • trunk/tests/phpunit/includes/phpunit7/testcase.php

    r44700 r44701  
    11<?php
    22
    3 require_once dirname( __FILE__ ) . '/factory.php';
    4 require_once dirname( __FILE__ ) . '/trac.php';
     3require_once dirname( dirname( __FILE__ ) ) . '/abstract-testcase.php';
    54
    65/**
     
    1312 * All WordPress unit tests should inherit from this class.
    1413 */
    15 class WP_UnitTestCase extends PHPUnit_Framework_TestCase {
    16 
    17     protected static $forced_tickets   = array();
    18     protected $expected_deprecated     = array();
    19     protected $caught_deprecated       = array();
    20     protected $expected_doing_it_wrong = array();
    21     protected $caught_doing_it_wrong   = array();
    22 
    23     protected static $hooks_saved = array();
    24     protected static $ignore_files;
    25 
    26     function __isset( $name ) {
    27         return 'factory' === $name;
    28     }
    29 
    30     function __get( $name ) {
    31         if ( 'factory' === $name ) {
    32             return self::factory();
    33         }
    34     }
    35 
    36     /**
    37      * Fetches the factory object for generating WordPress fixtures.
    38      *
    39      * @return WP_UnitTest_Factory The fixture factory.
    40      */
    41     protected static function factory() {
    42         static $factory = null;
    43         if ( ! $factory ) {
    44             $factory = new WP_UnitTest_Factory();
    45         }
    46         return $factory;
    47     }
    48 
    49     public static function get_called_class() {
    50         if ( function_exists( 'get_called_class' ) ) {
    51             return get_called_class();
    52         }
    53 
    54         // PHP 5.2 only
    55         $backtrace = debug_backtrace();
    56         // [0] WP_UnitTestCase::get_called_class()
    57         // [1] WP_UnitTestCase::setUpBeforeClass()
    58         if ( 'call_user_func' === $backtrace[2]['function'] ) {
    59             return $backtrace[2]['args'][0][0];
    60         }
    61         return $backtrace[2]['class'];
    62     }
    63 
    64     public static function setUpBeforeClass() {
    65         global $wpdb;
    66 
    67         $wpdb->suppress_errors = false;
    68         $wpdb->show_errors     = true;
    69         $wpdb->db_connect();
    70         ini_set( 'display_errors', 1 );
    71 
    72         parent::setUpBeforeClass();
    73 
    74         $c = self::get_called_class();
    75         if ( ! method_exists( $c, 'wpSetUpBeforeClass' ) ) {
    76             self::commit_transaction();
    77             return;
    78         }
    79 
    80         call_user_func( array( $c, 'wpSetUpBeforeClass' ), self::factory() );
    81 
    82         self::commit_transaction();
    83     }
    84 
    85     public static function tearDownAfterClass() {
    86         parent::tearDownAfterClass();
    87 
    88         _delete_all_data();
    89         self::flush_cache();
    90 
    91         $c = self::get_called_class();
    92         if ( ! method_exists( $c, 'wpTearDownAfterClass' ) ) {
    93             self::commit_transaction();
    94             return;
    95         }
    96 
    97         call_user_func( array( $c, 'wpTearDownAfterClass' ) );
    98 
    99         self::commit_transaction();
    100     }
    101 
    102     function setUp() {
    103         set_time_limit( 0 );
    104 
    105         if ( ! self::$ignore_files ) {
    106             self::$ignore_files = $this->scan_user_uploads();
    107         }
    108 
    109         if ( ! self::$hooks_saved ) {
    110             $this->_backup_hooks();
    111         }
    112 
    113         global $wp_rewrite;
    114 
    115         $this->clean_up_global_scope();
    116 
    117         /*
    118          * When running core tests, ensure that post types and taxonomies
    119          * are reset for each test. We skip this step for non-core tests,
    120          * given the large number of plugins that register post types and
    121          * taxonomies at 'init'.
    122          */
    123         if ( defined( 'WP_RUN_CORE_TESTS' ) && WP_RUN_CORE_TESTS ) {
    124             $this->reset_post_types();
    125             $this->reset_taxonomies();
    126             $this->reset_post_statuses();
    127             $this->reset__SERVER();
    128 
    129             if ( $wp_rewrite->permalink_structure ) {
    130                 $this->set_permalink_structure( '' );
    131             }
    132         }
    133 
    134         $this->start_transaction();
    135         $this->expectDeprecated();
    136         add_filter( 'wp_die_handler', array( $this, 'get_wp_die_handler' ) );
    137     }
    138 
    139     /**
    140      * Detect post-test failure conditions.
    141      *
    142      * We use this method to detect expectedDeprecated and expectedIncorrectUsage annotations.
    143      *
    144      * @since 4.2.0
    145      */
    146     protected function assertPostConditions() {
    147         $this->expectedDeprecated();
    148     }
    149 
    150     /**
    151      * After a test method runs, reset any state in WordPress the test method might have changed.
    152      */
    153     function tearDown() {
    154         global $wpdb, $wp_query, $wp;
    155         $wpdb->query( 'ROLLBACK' );
    156         if ( is_multisite() ) {
    157             while ( ms_is_switched() ) {
    158                 restore_current_blog();
    159             }
    160         }
    161         $wp_query = new WP_Query();
    162         $wp       = new WP();
    163 
    164         // Reset globals related to the post loop and `setup_postdata()`.
    165         $post_globals = array( 'post', 'id', 'authordata', 'currentday', 'currentmonth', 'page', 'pages', 'multipage', 'more', 'numpages' );
    166         foreach ( $post_globals as $global ) {
    167             $GLOBALS[ $global ] = null;
    168         }
    169 
    170         $this->unregister_all_meta_keys();
    171         remove_theme_support( 'html5' );
    172         remove_filter( 'query', array( $this, '_create_temporary_tables' ) );
    173         remove_filter( 'query', array( $this, '_drop_temporary_tables' ) );
    174         remove_filter( 'wp_die_handler', array( $this, 'get_wp_die_handler' ) );
    175         $this->_restore_hooks();
    176         wp_set_current_user( 0 );
    177     }
    178 
    179     function clean_up_global_scope() {
    180         $_GET  = array();
    181         $_POST = array();
    182         self::flush_cache();
    183     }
    184 
    185     /**
    186      * Allow tests to be skipped on some automated runs
    187      *
    188      * For test runs on Travis for something other than trunk/master
    189      * we want to skip tests that only need to run for master.
    190      */
    191     public function skipOnAutomatedBranches() {
    192         // gentenv can be disabled
    193         if ( ! function_exists( 'getenv' ) ) {
    194             return false;
    195         }
    196 
    197         // https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables
    198         $travis_branch       = getenv( 'TRAVIS_BRANCH' );
    199         $travis_pull_request = getenv( 'TRAVIS_PULL_REQUEST' );
    200 
    201         if ( false !== $travis_pull_request && 'master' !== $travis_branch ) {
    202             $this->markTestSkipped( 'For automated test runs, this test is only run on trunk/master' );
    203         }
    204     }
    205 
    206     /**
    207      * Allow tests to be skipped when Multisite is not in use.
    208      *
    209      * Use in conjunction with the ms-required group.
    210      */
    211     public function skipWithoutMultisite() {
    212         if ( ! is_multisite() ) {
    213             $this->markTestSkipped( 'Test only runs on Multisite' );
    214         }
    215     }
    216 
    217     /**
    218      * Allow tests to be skipped when Multisite is in use.
    219      *
    220      * Use in conjunction with the ms-excluded group.
    221      */
    222     public function skipWithMultisite() {
    223         if ( is_multisite() ) {
    224             $this->markTestSkipped( 'Test does not run on Multisite' );
    225         }
    226     }
    227 
    228     /**
    229      * Unregister existing post types and register defaults.
    230      *
    231      * Run before each test in order to clean up the global scope, in case
    232      * a test forgets to unregister a post type on its own, or fails before
    233      * it has a chance to do so.
    234      */
    235     protected function reset_post_types() {
    236         foreach ( get_post_types( array(), 'objects' ) as $pt ) {
    237             if ( empty( $pt->tests_no_auto_unregister ) ) {
    238                 _unregister_post_type( $pt->name );
    239             }
    240         }
    241         create_initial_post_types();
    242     }
    243 
    244     /**
    245      * Unregister existing taxonomies and register defaults.
    246      *
    247      * Run before each test in order to clean up the global scope, in case
    248      * a test forgets to unregister a taxonomy on its own, or fails before
    249      * it has a chance to do so.
    250      */
    251     protected function reset_taxonomies() {
    252         foreach ( get_taxonomies() as $tax ) {
    253             _unregister_taxonomy( $tax );
    254         }
    255         create_initial_taxonomies();
    256     }
    257 
    258     /**
    259      * Unregister non-built-in post statuses.
    260      */
    261     protected function reset_post_statuses() {
    262         foreach ( get_post_stati( array( '_builtin' => false ) ) as $post_status ) {
    263             _unregister_post_status( $post_status );
    264         }
    265     }
    266 
    267     /**
    268      * Reset `$_SERVER` variables
    269      */
    270     protected function reset__SERVER() {
    271         tests_reset__SERVER();
    272     }
    273 
    274     /**
    275      * Saves the action and filter-related globals so they can be restored later.
    276      *
    277      * Stores $wp_actions, $wp_current_filter, and $wp_filter
    278      * on a class variable so they can be restored on tearDown() using _restore_hooks().
    279      *
    280      * @global array $wp_actions
    281      * @global array $wp_current_filter
    282      * @global array $wp_filter
    283      * @return void
    284      */
    285     protected function _backup_hooks() {
    286         $globals = array( 'wp_actions', 'wp_current_filter' );
    287         foreach ( $globals as $key ) {
    288             self::$hooks_saved[ $key ] = $GLOBALS[ $key ];
    289         }
    290         self::$hooks_saved['wp_filter'] = array();
    291         foreach ( $GLOBALS['wp_filter'] as $hook_name => $hook_object ) {
    292             self::$hooks_saved['wp_filter'][ $hook_name ] = clone $hook_object;
    293         }
    294     }
    295 
    296     /**
    297      * Restores the hook-related globals to their state at setUp()
    298      * so that future tests aren't affected by hooks set during this last test.
    299      *
    300      * @global array $wp_actions
    301      * @global array $wp_current_filter
    302      * @global array $wp_filter
    303      * @return void
    304      */
    305     protected function _restore_hooks() {
    306         $globals = array( 'wp_actions', 'wp_current_filter' );
    307         foreach ( $globals as $key ) {
    308             if ( isset( self::$hooks_saved[ $key ] ) ) {
    309                 $GLOBALS[ $key ] = self::$hooks_saved[ $key ];
    310             }
    311         }
    312         if ( isset( self::$hooks_saved['wp_filter'] ) ) {
    313             $GLOBALS['wp_filter'] = array();
    314             foreach ( self::$hooks_saved['wp_filter'] as $hook_name => $hook_object ) {
    315                 $GLOBALS['wp_filter'][ $hook_name ] = clone $hook_object;
    316             }
    317         }
    318     }
    319 
    320     static function flush_cache() {
    321         global $wp_object_cache;
    322         $wp_object_cache->group_ops      = array();
    323         $wp_object_cache->stats          = array();
    324         $wp_object_cache->memcache_debug = array();
    325         $wp_object_cache->cache          = array();
    326         if ( method_exists( $wp_object_cache, '__remoteset' ) ) {
    327             $wp_object_cache->__remoteset();
    328         }
    329         wp_cache_flush();
    330         wp_cache_add_global_groups( array( 'users', 'userlogins', 'usermeta', 'user_meta', 'useremail', 'userslugs', 'site-transient', 'site-options', 'blog-lookup', 'blog-details', 'rss', 'global-posts', 'blog-id-cache', 'networks', 'sites', 'site-details', 'blog_meta' ) );
    331         wp_cache_add_non_persistent_groups( array( 'comment', 'counts', 'plugins' ) );
    332     }
    333 
    334     /**
    335      * Clean up any registered meta keys.
    336      *
    337      * @since 5.1.0
    338      *
    339      * @global array $wp_meta_keys
    340      */
    341     function unregister_all_meta_keys() {
    342         global $wp_meta_keys;
    343         if ( ! is_array( $wp_meta_keys ) ) {
    344             return;
    345         }
    346         foreach ( $wp_meta_keys as $object_type => $type_keys ) {
    347             foreach ( $type_keys as $object_subtype => $subtype_keys ) {
    348                 foreach ( $subtype_keys as $key => $value ) {
    349                     unregister_meta_key( $object_type, $key, $object_subtype );
    350                 }
    351             }
    352         }
    353     }
    354 
    355     function start_transaction() {
    356         global $wpdb;
    357         $wpdb->query( 'SET autocommit = 0;' );
    358         $wpdb->query( 'START TRANSACTION;' );
    359         add_filter( 'query', array( $this, '_create_temporary_tables' ) );
    360         add_filter( 'query', array( $this, '_drop_temporary_tables' ) );
    361     }
    362 
    363     /**
    364      * Commit the queries in a transaction.
    365      *
    366      * @since 4.1.0
    367      */
    368     public static function commit_transaction() {
    369         global $wpdb;
    370         $wpdb->query( 'COMMIT;' );
    371     }
    372 
    373     function _create_temporary_tables( $query ) {
    374         if ( 'CREATE TABLE' === substr( trim( $query ), 0, 12 ) ) {
    375             return substr_replace( trim( $query ), 'CREATE TEMPORARY TABLE', 0, 12 );
    376         }
    377         return $query;
    378     }
    379 
    380     function _drop_temporary_tables( $query ) {
    381         if ( 'DROP TABLE' === substr( trim( $query ), 0, 10 ) ) {
    382             return substr_replace( trim( $query ), 'DROP TEMPORARY TABLE', 0, 10 );
    383         }
    384         return $query;
    385     }
    386 
    387     function get_wp_die_handler( $handler ) {
    388         return array( $this, 'wp_die_handler' );
    389     }
    390 
    391     function wp_die_handler( $message ) {
    392         if ( ! is_scalar( $message ) ) {
    393             $message = '0';
    394         }
    395 
    396         throw new WPDieException( $message );
    397     }
    398 
    399     function expectDeprecated() {
    400         $annotations = $this->getAnnotations();
    401         foreach ( array( 'class', 'method' ) as $depth ) {
    402             if ( ! empty( $annotations[ $depth ]['expectedDeprecated'] ) ) {
    403                 $this->expected_deprecated = array_merge( $this->expected_deprecated, $annotations[ $depth ]['expectedDeprecated'] );
    404             }
    405             if ( ! empty( $annotations[ $depth ]['expectedIncorrectUsage'] ) ) {
    406                 $this->expected_doing_it_wrong = array_merge( $this->expected_doing_it_wrong, $annotations[ $depth ]['expectedIncorrectUsage'] );
    407             }
    408         }
    409         add_action( 'deprecated_function_run', array( $this, 'deprecated_function_run' ) );
    410         add_action( 'deprecated_argument_run', array( $this, 'deprecated_function_run' ) );
    411         add_action( 'deprecated_hook_run', array( $this, 'deprecated_function_run' ) );
    412         add_action( 'doing_it_wrong_run', array( $this, 'doing_it_wrong_run' ) );
    413         add_action( 'deprecated_function_trigger_error', '__return_false' );
    414         add_action( 'deprecated_argument_trigger_error', '__return_false' );
    415         add_action( 'deprecated_hook_trigger_error', '__return_false' );
    416         add_action( 'doing_it_wrong_trigger_error', '__return_false' );
    417     }
    418 
    419     function expectedDeprecated() {
    420         $errors = array();
    421 
    422         $not_caught_deprecated = array_diff( $this->expected_deprecated, $this->caught_deprecated );
    423         foreach ( $not_caught_deprecated as $not_caught ) {
    424             $errors[] = "Failed to assert that $not_caught triggered a deprecated notice";
    425         }
    426 
    427         $unexpected_deprecated = array_diff( $this->caught_deprecated, $this->expected_deprecated );
    428         foreach ( $unexpected_deprecated as $unexpected ) {
    429             $errors[] = "Unexpected deprecated notice for $unexpected";
    430         }
    431 
    432         $not_caught_doing_it_wrong = array_diff( $this->expected_doing_it_wrong, $this->caught_doing_it_wrong );
    433         foreach ( $not_caught_doing_it_wrong as $not_caught ) {
    434             $errors[] = "Failed to assert that $not_caught triggered an incorrect usage notice";
    435         }
    436 
    437         $unexpected_doing_it_wrong = array_diff( $this->caught_doing_it_wrong, $this->expected_doing_it_wrong );
    438         foreach ( $unexpected_doing_it_wrong as $unexpected ) {
    439             $errors[] = "Unexpected incorrect usage notice for $unexpected";
    440         }
    441 
    442         // Perform an assertion, but only if there are expected or unexpected deprecated calls or wrongdoings
    443         if ( ! empty( $this->expected_deprecated ) ||
    444             ! empty( $this->expected_doing_it_wrong ) ||
    445             ! empty( $this->caught_deprecated ) ||
    446             ! empty( $this->caught_doing_it_wrong ) ) {
    447             $this->assertEmpty( $errors, implode( "\n", $errors ) );
    448         }
    449     }
    450 
    451     /**
    452      * Declare an expected `_deprecated_function()` or `_deprecated_argument()` call from within a test.
    453      *
    454      * @since 4.2.0
    455      *
    456      * @param string $deprecated Name of the function, method, class, or argument that is deprecated. Must match
    457      *                           first parameter of the `_deprecated_function()` or `_deprecated_argument()` call.
    458      */
    459     public function setExpectedDeprecated( $deprecated ) {
    460         array_push( $this->expected_deprecated, $deprecated );
    461     }
    462 
    463     /**
    464      * Declare an expected `_doing_it_wrong()` call from within a test.
    465      *
    466      * @since 4.2.0
    467      *
    468      * @param string $deprecated Name of the function, method, or class that appears in the first argument of the
    469      *                           source `_doing_it_wrong()` call.
    470      */
    471     public function setExpectedIncorrectUsage( $doing_it_wrong ) {
    472         array_push( $this->expected_doing_it_wrong, $doing_it_wrong );
    473     }
    474 
    475     /**
    476      * PHPUnit 6+ compatibility shim.
    477      *
    478      * @param mixed      $exception
    479      * @param string     $message
    480      * @param int|string $code
    481      */
    482     public function setExpectedException( $exception, $message = '', $code = null ) {
    483         if ( method_exists( 'PHPUnit_Framework_TestCase', 'setExpectedException' ) ) {
    484             parent::setExpectedException( $exception, $message, $code );
    485         } else {
    486             $this->expectException( $exception );
    487             if ( '' !== $message ) {
    488                 $this->expectExceptionMessage( $message );
    489             }
    490             if ( null !== $code ) {
    491                 $this->expectExceptionCode( $code );
    492             }
    493         }
    494     }
    495 
    496     function deprecated_function_run( $function ) {
    497         if ( ! in_array( $function, $this->caught_deprecated ) ) {
    498             $this->caught_deprecated[] = $function;
    499         }
    500     }
    501 
    502     function doing_it_wrong_run( $function ) {
    503         if ( ! in_array( $function, $this->caught_doing_it_wrong ) ) {
    504             $this->caught_doing_it_wrong[] = $function;
    505         }
    506     }
    507 
    508     function assertWPError( $actual, $message = '' ) {
    509         $this->assertInstanceOf( 'WP_Error', $actual, $message );
    510     }
    511 
    512     function assertNotWPError( $actual, $message = '' ) {
    513         if ( is_wp_error( $actual ) && '' === $message ) {
    514             $message = $actual->get_error_message();
    515         }
    516         $this->assertNotInstanceOf( 'WP_Error', $actual, $message );
    517     }
    518 
    519     function assertIXRError( $actual, $message = '' ) {
    520         $this->assertInstanceOf( 'IXR_Error', $actual, $message );
    521     }
    522 
    523     function assertNotIXRError( $actual, $message = '' ) {
    524         if ( $actual instanceof IXR_Error && '' === $message ) {
    525             $message = $actual->message;
    526         }
    527         $this->assertNotInstanceOf( 'IXR_Error', $actual, $message );
    528     }
    529 
    530     function assertEqualFields( $object, $fields ) {
    531         foreach ( $fields as $field_name => $field_value ) {
    532             if ( $object->$field_name != $field_value ) {
    533                 $this->fail();
    534             }
    535         }
    536     }
    537 
    538     function assertDiscardWhitespace( $expected, $actual ) {
    539         $this->assertEquals( preg_replace( '/\s*/', '', $expected ), preg_replace( '/\s*/', '', $actual ) );
    540     }
    541 
    542     /**
    543      * Asserts that the contents of two un-keyed, single arrays are equal, without accounting for the order of elements.
    544      *
    545      * @since 3.5.0
    546      *
    547      * @param array $expected Expected array.
    548      * @param array $actual   Array to check.
    549      */
    550     function assertEqualSets( $expected, $actual ) {
    551         sort( $expected );
    552         sort( $actual );
    553         $this->assertEquals( $expected, $actual );
    554     }
    555 
    556     /**
    557      * Asserts that the contents of two keyed, single arrays are equal, without accounting for the order of elements.
    558      *
    559      * @since 4.1.0
    560      *
    561      * @param array $expected Expected array.
    562      * @param array $actual   Array to check.
    563      */
    564     function assertEqualSetsWithIndex( $expected, $actual ) {
    565         ksort( $expected );
    566         ksort( $actual );
    567         $this->assertEquals( $expected, $actual );
    568     }
    569 
    570     /**
    571      * Asserts that the given variable is a multidimensional array, and that all arrays are non-empty.
    572      *
    573      * @since 4.8.0
    574      *
    575      * @param array $array Array to check.
    576      */
    577     function assertNonEmptyMultidimensionalArray( $array ) {
    578         $this->assertTrue( is_array( $array ) );
    579         $this->assertNotEmpty( $array );
    580 
    581         foreach ( $array as $sub_array ) {
    582             $this->assertTrue( is_array( $sub_array ) );
    583             $this->assertNotEmpty( $sub_array );
    584         }
    585     }
    586 
     14class WP_UnitTestCase extends WP_UnitTestCase_Base {
    58715    /**
    58816     * Asserts that a condition is not false.
     
    59826     * @throws PHPUnit_Framework_AssertionFailedError
    59927     */
    600     public static function assertNotFalse( $condition, $message = '' ) {
     28    public static function assertNotFalse( $condition, string $message = '' ): void {
    60129        self::assertThat( $condition, self::logicalNot( self::isFalse() ), $message );
    60230    }
    603 
    604     /**
    605      * Sets the global state to as if a given URL has been requested.
    606      *
    607      * This sets:
    608      * - The super globals.
    609      * - The globals.
    610      * - The query variables.
    611      * - The main query.
    612      *
    613      * @since 3.5.0
    614      *
    615      * @param string $url The URL for the request.
    616      */
    617     function go_to( $url ) {
    618         // note: the WP and WP_Query classes like to silently fetch parameters
    619         // from all over the place (globals, GET, etc), which makes it tricky
    620         // to run them more than once without very carefully clearing everything
    621         $_GET = $_POST = array();
    622         foreach ( array( 'query_string', 'id', 'postdata', 'authordata', 'day', 'currentmonth', 'page', 'pages', 'multipage', 'more', 'numpages', 'pagenow' ) as $v ) {
    623             if ( isset( $GLOBALS[ $v ] ) ) {
    624                 unset( $GLOBALS[ $v ] );
    625             }
    626         }
    627         $parts = parse_url( $url );
    628         if ( isset( $parts['scheme'] ) ) {
    629             $req = isset( $parts['path'] ) ? $parts['path'] : '';
    630             if ( isset( $parts['query'] ) ) {
    631                 $req .= '?' . $parts['query'];
    632                 // parse the url query vars into $_GET
    633                 parse_str( $parts['query'], $_GET );
    634             }
    635         } else {
    636             $req = $url;
    637         }
    638         if ( ! isset( $parts['query'] ) ) {
    639             $parts['query'] = '';
    640         }
    641 
    642         $_SERVER['REQUEST_URI'] = $req;
    643         unset( $_SERVER['PATH_INFO'] );
    644 
    645         self::flush_cache();
    646         unset( $GLOBALS['wp_query'], $GLOBALS['wp_the_query'] );
    647         $GLOBALS['wp_the_query'] = new WP_Query();
    648         $GLOBALS['wp_query']     = $GLOBALS['wp_the_query'];
    649 
    650         $public_query_vars  = $GLOBALS['wp']->public_query_vars;
    651         $private_query_vars = $GLOBALS['wp']->private_query_vars;
    652 
    653         $GLOBALS['wp']                     = new WP();
    654         $GLOBALS['wp']->public_query_vars  = $public_query_vars;
    655         $GLOBALS['wp']->private_query_vars = $private_query_vars;
    656 
    657         _cleanup_query_vars();
    658 
    659         $GLOBALS['wp']->main( $parts['query'] );
    660     }
    661 
    662     /**
    663      * Allows tests to be skipped on single or multisite installs by using @group annotations.
    664      *
    665      * This is a custom extension of the PHPUnit requirements handling.
    666      *
    667      * Contains legacy code for skipping tests that are associated with an open Trac ticket. Core tests no longer
    668      * support this behaviour.
    669      *
    670      * @since 3.5.0
    671      */
    672     protected function checkRequirements() {
    673         parent::checkRequirements();
    674 
    675         $annotations = $this->getAnnotations();
    676 
    677         $groups = array();
    678         if ( ! empty( $annotations['class']['group'] ) ) {
    679             $groups = array_merge( $groups, $annotations['class']['group'] );
    680         }
    681         if ( ! empty( $annotations['method']['group'] ) ) {
    682             $groups = array_merge( $groups, $annotations['method']['group'] );
    683         }
    684 
    685         if ( ! empty( $groups ) ) {
    686             if ( in_array( 'ms-required', $groups, true ) ) {
    687                 $this->skipWithoutMultisite();
    688             }
    689 
    690             if ( in_array( 'ms-excluded', $groups, true ) ) {
    691                 $this->skipWithMultisite();
    692             }
    693         }
    694 
    695         // Core tests no longer check against open Trac tickets, but others using WP_UnitTestCase may do so.
    696         if ( defined( 'WP_RUN_CORE_TESTS' ) && WP_RUN_CORE_TESTS ) {
    697             return;
    698         }
    699 
    700         if ( WP_TESTS_FORCE_KNOWN_BUGS ) {
    701             return;
    702         }
    703         $tickets = PHPUnit_Util_Test::getTickets( get_class( $this ), $this->getName( false ) );
    704         foreach ( $tickets as $ticket ) {
    705             if ( is_numeric( $ticket ) ) {
    706                 $this->knownWPBug( $ticket );
    707             } elseif ( 'Plugin' == substr( $ticket, 0, 6 ) ) {
    708                 $ticket = substr( $ticket, 6 );
    709                 if ( $ticket && is_numeric( $ticket ) ) {
    710                     $this->knownPluginBug( $ticket );
    711                 }
    712             }
    713         }
    714     }
    715 
    716     /**
    717      * Skips the current test if there is an open Trac ticket associated with it.
    718      *
    719      * @since 3.5.0
    720      *
    721      * @param int $ticket_id Ticket number.
    722      */
    723     function knownWPBug( $ticket_id ) {
    724         if ( WP_TESTS_FORCE_KNOWN_BUGS || in_array( $ticket_id, self::$forced_tickets ) ) {
    725             return;
    726         }
    727         if ( ! TracTickets::isTracTicketClosed( 'https://core.trac.wordpress.org', $ticket_id ) ) {
    728             $this->markTestSkipped( sprintf( 'WordPress Ticket #%d is not fixed', $ticket_id ) );
    729         }
    730     }
    731 
    732     /**
    733      * Skips the current test if there is an open Unit Test Trac ticket associated with it.
    734      *
    735      * @since 3.5.0
    736      *
    737      * @deprecated No longer used since the Unit Test Trac was merged into the Core Trac.
    738      *
    739      * @param int $ticket_id Ticket number.
    740      */
    741     function knownUTBug( $ticket_id ) {
    742         return;
    743     }
    744 
    745     /**
    746      * Skips the current test if there is an open Plugin Trac ticket associated with it.
    747      *
    748      * @since 3.5.0
    749      *
    750      * @param int $ticket_id Ticket number.
    751      */
    752     function knownPluginBug( $ticket_id ) {
    753         if ( WP_TESTS_FORCE_KNOWN_BUGS || in_array( 'Plugin' . $ticket_id, self::$forced_tickets ) ) {
    754             return;
    755         }
    756         if ( ! TracTickets::isTracTicketClosed( 'https://plugins.trac.wordpress.org', $ticket_id ) ) {
    757             $this->markTestSkipped( sprintf( 'WordPress Plugin Ticket #%d is not fixed', $ticket_id ) );
    758         }
    759     }
    760 
    761     /**
    762      * Adds a Trac ticket number to the `$forced_tickets` property.
    763      *
    764      * @since 3.5.0
    765      *
    766      * @param int $ticket Ticket number.
    767      */
    768     public static function forceTicket( $ticket ) {
    769         self::$forced_tickets[] = $ticket;
    770     }
    771 
    772     /**
    773      * Custom preparations for the PHPUnit process isolation template.
    774      *
    775      * When restoring global state between tests, PHPUnit defines all the constants that were already defined, and then
    776      * includes included files. This does not work with WordPress, as the included files define the constants.
    777      *
    778      * This method defines the constants after including files.
    779      *
    780      * @param Text_Template $template
    781      */
    782     function prepareTemplate( Text_Template $template ) {
    783         $template->setVar( array( 'constants' => '' ) );
    784         $template->setVar( array( 'wp_constants' => PHPUnit_Util_GlobalState::getConstantsAsString() ) );
    785         parent::prepareTemplate( $template );
    786     }
    787 
    788     /**
    789      * Creates a unique temporary file name.
    790      *
    791      * The directory in which the file is created depends on the environment configuration.
    792      *
    793      * @since 3.5.0
    794      *
    795      * @return string|bool Path on success, else false.
    796      */
    797     function temp_filename() {
    798         $tmp_dir = '';
    799         $dirs    = array( 'TMP', 'TMPDIR', 'TEMP' );
    800         foreach ( $dirs as $dir ) {
    801             if ( isset( $_ENV[ $dir ] ) && ! empty( $_ENV[ $dir ] ) ) {
    802                 $tmp_dir = $dir;
    803                 break;
    804             }
    805         }
    806         if ( empty( $tmp_dir ) ) {
    807             $tmp_dir = '/tmp';
    808         }
    809         $tmp_dir = realpath( $tmp_dir );
    810         return tempnam( $tmp_dir, 'wpunit' );
    811     }
    812 
    813     /**
    814      * Checks each of the WP_Query is_* functions/properties against expected boolean value.
    815      *
    816      * Any properties that are listed by name as parameters will be expected to be true; all others are
    817      * expected to be false. For example, assertQueryTrue('is_single', 'is_feed') means is_single()
    818      * and is_feed() must be true and everything else must be false to pass.
    819      *
    820      * @since 2.5.0
    821      * @since 3.8.0 Moved from `Tests_Query_Conditionals` to `WP_UnitTestCase`.
    822      *
    823      * @param string $prop,... Any number of WP_Query properties that are expected to be true for the current request.
    824      */
    825     function assertQueryTrue() {
    826         global $wp_query;
    827         $all  = array(
    828             'is_404',
    829             'is_admin',
    830             'is_archive',
    831             'is_attachment',
    832             'is_author',
    833             'is_category',
    834             'is_comment_feed',
    835             'is_date',
    836             'is_day',
    837             'is_embed',
    838             'is_feed',
    839             'is_front_page',
    840             'is_home',
    841             'is_month',
    842             'is_page',
    843             'is_paged',
    844             'is_post_type_archive',
    845             'is_posts_page',
    846             'is_preview',
    847             'is_robots',
    848             'is_search',
    849             'is_single',
    850             'is_singular',
    851             'is_tag',
    852             'is_tax',
    853             'is_time',
    854             'is_trackback',
    855             'is_year',
    856         );
    857         $true = func_get_args();
    858 
    859         foreach ( $true as $true_thing ) {
    860             $this->assertContains( $true_thing, $all, "Unknown conditional: {$true_thing}." );
    861         }
    862 
    863         $passed  = true;
    864         $message = '';
    865 
    866         foreach ( $all as $query_thing ) {
    867             $result = is_callable( $query_thing ) ? call_user_func( $query_thing ) : $wp_query->$query_thing;
    868 
    869             if ( in_array( $query_thing, $true ) ) {
    870                 if ( ! $result ) {
    871                     $message .= $query_thing . ' is false but is expected to be true. ' . PHP_EOL;
    872                     $passed   = false;
    873                 }
    874             } elseif ( $result ) {
    875                 $message .= $query_thing . ' is true but is expected to be false. ' . PHP_EOL;
    876                 $passed   = false;
    877             }
    878         }
    879 
    880         if ( ! $passed ) {
    881             $this->fail( $message );
    882         }
    883     }
    884 
    885     /**
    886      * Selectively deletes a file.
    887      *
    888      * Does not delete a file if its path is set in the `$ignore_files` property.
    889      *
    890      * @param string $file File path.
    891      */
    892     function unlink( $file ) {
    893         $exists = is_file( $file );
    894         if ( $exists && ! in_array( $file, self::$ignore_files ) ) {
    895             //error_log( $file );
    896             unlink( $file );
    897         } elseif ( ! $exists ) {
    898             $this->fail( "Trying to delete a file that doesn't exist: $file" );
    899         }
    900     }
    901 
    902     /**
    903      * Selectively deletes files from a directory.
    904      *
    905      * Does not delete files if their paths are set in the `$ignore_files` property.
    906      *
    907      * @param string $path Directory path.
    908      */
    909     function rmdir( $path ) {
    910         $files = $this->files_in_dir( $path );
    911         foreach ( $files as $file ) {
    912             if ( ! in_array( $file, self::$ignore_files ) ) {
    913                 $this->unlink( $file );
    914             }
    915         }
    916     }
    917 
    918     /**
    919      * Deletes files added to the `uploads` directory during tests.
    920      *
    921      * This method works in tandem with the `setUp()` and `rmdir()` methods:
    922      * - `setUp()` scans the `uploads` directory before every test, and stores its contents inside of the
    923      *   `$ignore_files` property.
    924      * - `rmdir()` and its helper methods only delete files that are not listed in the `$ignore_files` property. If
    925      *   called during `tearDown()` in tests, this will only delete files added during the previously run test.
    926      */
    927     function remove_added_uploads() {
    928         $uploads = wp_upload_dir();
    929         $this->rmdir( $uploads['basedir'] );
    930     }
    931 
    932     /**
    933      * Returns a list of all files contained inside a directory.
    934      *
    935      * @since 4.0.0
    936      *
    937      * @param string $dir Path to the directory to scan.
    938      *
    939      * @return array List of file paths.
    940      */
    941     function files_in_dir( $dir ) {
    942         $files = array();
    943 
    944         $iterator = new RecursiveDirectoryIterator( $dir );
    945         $objects  = new RecursiveIteratorIterator( $iterator );
    946         foreach ( $objects as $name => $object ) {
    947             if ( is_file( $name ) ) {
    948                 $files[] = $name;
    949             }
    950         }
    951 
    952         return $files;
    953     }
    954 
    955     /**
    956      * Returns a list of all files contained inside the `uploads` directory.
    957      *
    958      * @since 4.0.0
    959      *
    960      * @return array List of file paths.
    961      */
    962     function scan_user_uploads() {
    963         static $files = array();
    964         if ( ! empty( $files ) ) {
    965             return $files;
    966         }
    967 
    968         $uploads = wp_upload_dir();
    969         $files   = $this->files_in_dir( $uploads['basedir'] );
    970         return $files;
    971     }
    972 
    973     /**
    974      * Deletes all directories contained inside a directory.
    975      *
    976      * @since 4.1.0
    977      *
    978      * @param string $path Path to the directory to scan.
    979      */
    980     function delete_folders( $path ) {
    981         $this->matched_dirs = array();
    982         if ( ! is_dir( $path ) ) {
    983             return;
    984         }
    985 
    986         $this->scandir( $path );
    987         foreach ( array_reverse( $this->matched_dirs ) as $dir ) {
    988             rmdir( $dir );
    989         }
    990         rmdir( $path );
    991     }
    992 
    993     /**
    994      * Retrieves all directories contained inside a directory and stores them in the `$matched_dirs` property. Hidden
    995      * directories are ignored.
    996      *
    997      * This is a helper for the `delete_folders()` method.
    998      *
    999      * @since 4.1.0
    1000      *
    1001      * @param string $dir Path to the directory to scan.
    1002      */
    1003     function scandir( $dir ) {
    1004         foreach ( scandir( $dir ) as $path ) {
    1005             if ( 0 !== strpos( $path, '.' ) && is_dir( $dir . '/' . $path ) ) {
    1006                 $this->matched_dirs[] = $dir . '/' . $path;
    1007                 $this->scandir( $dir . '/' . $path );
    1008             }
    1009         }
    1010     }
    1011 
    1012     /**
    1013      * Converts a microtime string into a float.
    1014      *
    1015      * @since 4.1.0
    1016      *
    1017      * @param string $microtime Time string generated by `microtime()`.
    1018      *
    1019      * @return float `microtime()` output as a float.
    1020      */
    1021     protected function _microtime_to_float( $microtime ) {
    1022         $time_array = explode( ' ', $microtime );
    1023         return array_sum( $time_array );
    1024     }
    1025 
    1026     /**
    1027      * Deletes a user from the database in a Multisite-agnostic way.
    1028      *
    1029      * @since 4.3.0
    1030      *
    1031      * @param int $user_id User ID.
    1032      *
    1033      * @return bool True if the user was deleted.
    1034      */
    1035     public static function delete_user( $user_id ) {
    1036         if ( is_multisite() ) {
    1037             return wpmu_delete_user( $user_id );
    1038         } else {
    1039             return wp_delete_user( $user_id );
    1040         }
    1041     }
    1042 
    1043     /**
    1044      * Resets permalinks and flushes rewrites.
    1045      *
    1046      * @since 4.4.0
    1047      *
    1048      * @global WP_Rewrite $wp_rewrite
    1049      *
    1050      * @param string $structure Optional. Permalink structure to set. Default empty.
    1051      */
    1052     public function set_permalink_structure( $structure = '' ) {
    1053         global $wp_rewrite;
    1054 
    1055         $wp_rewrite->init();
    1056         $wp_rewrite->set_permalink_structure( $structure );
    1057         $wp_rewrite->flush_rules();
    1058     }
    1059 
    1060     /**
    1061      * Creates an attachment post from an uploaded file.
    1062      *
    1063      * @since 4.4.0
    1064      *
    1065      * @param array $upload         Array of information about the uploaded file, provided by wp_upload_bits().
    1066      * @param int   $parent_post_id Optional. Parent post ID.
    1067      *
    1068      * @return int|WP_Error The attachment ID on success. The value 0 or WP_Error on failure.
    1069      */
    1070     function _make_attachment( $upload, $parent_post_id = 0 ) {
    1071         $type = '';
    1072         if ( ! empty( $upload['type'] ) ) {
    1073             $type = $upload['type'];
    1074         } else {
    1075             $mime = wp_check_filetype( $upload['file'] );
    1076             if ( $mime ) {
    1077                 $type = $mime['type'];
    1078             }
    1079         }
    1080 
    1081         $attachment = array(
    1082             'post_title'     => basename( $upload['file'] ),
    1083             'post_content'   => '',
    1084             'post_type'      => 'attachment',
    1085             'post_parent'    => $parent_post_id,
    1086             'post_mime_type' => $type,
    1087             'guid'           => $upload['url'],
    1088         );
    1089 
    1090         $id = wp_insert_attachment( $attachment, $upload['file'], $parent_post_id );
    1091         wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $upload['file'] ) );
    1092         return $id;
    1093     }
    1094 
    1095     /**
    1096      * Updates the modified and modified GMT date of a post in the database.
    1097      *
    1098      * @since 4.8.0
    1099      *
    1100      * @global wpdb $wpdb WordPress database abstraction object.
    1101      *
    1102      * @param int    $post_id Post ID.
    1103      * @param string $date    Post date, in the format YYYY-MM-DD HH:MM:SS.
    1104      *
    1105      * @return int|false 1 on success, or false on error.
    1106      */
    1107     protected function update_post_modified( $post_id, $date ) {
    1108         global $wpdb;
    1109         return $wpdb->update(
    1110             $wpdb->posts,
    1111             array(
    1112                 'post_modified'     => $date,
    1113                 'post_modified_gmt' => $date,
    1114             ),
    1115             array(
    1116                 'ID' => $post_id,
    1117             ),
    1118             array(
    1119                 '%s',
    1120                 '%s',
    1121             ),
    1122             array(
    1123                 '%d',
    1124             )
    1125         );
    1126     }
    112731}
  • trunk/tests/phpunit/includes/testcase-canonical.php

    r42343 r44701  
    237237        $this->expected_doing_it_wrong = array_merge( $this->expected_doing_it_wrong, (array) $expected_doing_it_wrong );
    238238
    239         $ticket_ref = ( $ticket > 0 ) ? 'Ticket #' . $ticket : null;
     239        $ticket_ref = ( $ticket > 0 ) ? 'Ticket #' . $ticket : '';
    240240
    241241        if ( is_string( $expected ) ) {
  • trunk/tests/phpunit/includes/testcase.php

    r44633 r44701  
    11<?php
    22
    3 require_once dirname( __FILE__ ) . '/factory.php';
    4 require_once dirname( __FILE__ ) . '/trac.php';
     3require_once dirname( __FILE__ ) . '/abstract-testcase.php';
    54
    65/**
     
    1312 * All WordPress unit tests should inherit from this class.
    1413 */
    15 class WP_UnitTestCase extends PHPUnit_Framework_TestCase {
    16 
    17     protected static $forced_tickets   = array();
    18     protected $expected_deprecated     = array();
    19     protected $caught_deprecated       = array();
    20     protected $expected_doing_it_wrong = array();
    21     protected $caught_doing_it_wrong   = array();
    22 
    23     protected static $hooks_saved = array();
    24     protected static $ignore_files;
    25 
    26     function __isset( $name ) {
    27         return 'factory' === $name;
    28     }
    29 
    30     function __get( $name ) {
    31         if ( 'factory' === $name ) {
    32             return self::factory();
    33         }
    34     }
    35 
    36     /**
    37      * Fetches the factory object for generating WordPress fixtures.
    38      *
    39      * @return WP_UnitTest_Factory The fixture factory.
    40      */
    41     protected static function factory() {
    42         static $factory = null;
    43         if ( ! $factory ) {
    44             $factory = new WP_UnitTest_Factory();
    45         }
    46         return $factory;
    47     }
    48 
    49     public static function get_called_class() {
    50         if ( function_exists( 'get_called_class' ) ) {
    51             return get_called_class();
    52         }
    53 
    54         // PHP 5.2 only
    55         $backtrace = debug_backtrace();
    56         // [0] WP_UnitTestCase::get_called_class()
    57         // [1] WP_UnitTestCase::setUpBeforeClass()
    58         if ( 'call_user_func' === $backtrace[2]['function'] ) {
    59             return $backtrace[2]['args'][0][0];
    60         }
    61         return $backtrace[2]['class'];
    62     }
    63 
    64     public static function setUpBeforeClass() {
    65         global $wpdb;
    66 
    67         $wpdb->suppress_errors = false;
    68         $wpdb->show_errors     = true;
    69         $wpdb->db_connect();
    70         ini_set( 'display_errors', 1 );
    71 
    72         parent::setUpBeforeClass();
    73 
    74         $c = self::get_called_class();
    75         if ( ! method_exists( $c, 'wpSetUpBeforeClass' ) ) {
    76             self::commit_transaction();
    77             return;
    78         }
    79 
    80         call_user_func( array( $c, 'wpSetUpBeforeClass' ), self::factory() );
    81 
    82         self::commit_transaction();
    83     }
    84 
    85     public static function tearDownAfterClass() {
    86         parent::tearDownAfterClass();
    87 
    88         _delete_all_data();
    89         self::flush_cache();
    90 
    91         $c = self::get_called_class();
    92         if ( ! method_exists( $c, 'wpTearDownAfterClass' ) ) {
    93             self::commit_transaction();
    94             return;
    95         }
    96 
    97         call_user_func( array( $c, 'wpTearDownAfterClass' ) );
    98 
    99         self::commit_transaction();
    100     }
    101 
    102     function setUp() {
    103         set_time_limit( 0 );
    104 
    105         if ( ! self::$ignore_files ) {
    106             self::$ignore_files = $this->scan_user_uploads();
    107         }
    108 
    109         if ( ! self::$hooks_saved ) {
    110             $this->_backup_hooks();
    111         }
    112 
    113         global $wp_rewrite;
    114 
    115         $this->clean_up_global_scope();
    116 
    117         /*
    118          * When running core tests, ensure that post types and taxonomies
    119          * are reset for each test. We skip this step for non-core tests,
    120          * given the large number of plugins that register post types and
    121          * taxonomies at 'init'.
    122          */
    123         if ( defined( 'WP_RUN_CORE_TESTS' ) && WP_RUN_CORE_TESTS ) {
    124             $this->reset_post_types();
    125             $this->reset_taxonomies();
    126             $this->reset_post_statuses();
    127             $this->reset__SERVER();
    128 
    129             if ( $wp_rewrite->permalink_structure ) {
    130                 $this->set_permalink_structure( '' );
    131             }
    132         }
    133 
    134         $this->start_transaction();
    135         $this->expectDeprecated();
    136         add_filter( 'wp_die_handler', array( $this, 'get_wp_die_handler' ) );
    137     }
    138 
    139     /**
    140      * Detect post-test failure conditions.
    141      *
    142      * We use this method to detect expectedDeprecated and expectedIncorrectUsage annotations.
    143      *
    144      * @since 4.2.0
    145      */
    146     protected function assertPostConditions() {
    147         $this->expectedDeprecated();
    148     }
    149 
    150     /**
    151      * After a test method runs, reset any state in WordPress the test method might have changed.
    152      */
    153     function tearDown() {
    154         global $wpdb, $wp_query, $wp;
    155         $wpdb->query( 'ROLLBACK' );
    156         if ( is_multisite() ) {
    157             while ( ms_is_switched() ) {
    158                 restore_current_blog();
    159             }
    160         }
    161         $wp_query = new WP_Query();
    162         $wp       = new WP();
    163 
    164         // Reset globals related to the post loop and `setup_postdata()`.
    165         $post_globals = array( 'post', 'id', 'authordata', 'currentday', 'currentmonth', 'page', 'pages', 'multipage', 'more', 'numpages' );
    166         foreach ( $post_globals as $global ) {
    167             $GLOBALS[ $global ] = null;
    168         }
    169 
    170         $this->unregister_all_meta_keys();
    171         remove_theme_support( 'html5' );
    172         remove_filter( 'query', array( $this, '_create_temporary_tables' ) );
    173         remove_filter( 'query', array( $this, '_drop_temporary_tables' ) );
    174         remove_filter( 'wp_die_handler', array( $this, 'get_wp_die_handler' ) );
    175         $this->_restore_hooks();
    176         wp_set_current_user( 0 );
    177     }
    178 
    179     function clean_up_global_scope() {
    180         $_GET  = array();
    181         $_POST = array();
    182         self::flush_cache();
    183     }
    184 
    185     /**
    186      * Allow tests to be skipped on some automated runs
    187      *
    188      * For test runs on Travis for something other than trunk/master
    189      * we want to skip tests that only need to run for master.
    190      */
    191     public function skipOnAutomatedBranches() {
    192         // gentenv can be disabled
    193         if ( ! function_exists( 'getenv' ) ) {
    194             return false;
    195         }
    196 
    197         // https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables
    198         $travis_branch       = getenv( 'TRAVIS_BRANCH' );
    199         $travis_pull_request = getenv( 'TRAVIS_PULL_REQUEST' );
    200 
    201         if ( false !== $travis_pull_request && 'master' !== $travis_branch ) {
    202             $this->markTestSkipped( 'For automated test runs, this test is only run on trunk/master' );
    203         }
    204     }
    205 
    206     /**
    207      * Allow tests to be skipped when Multisite is not in use.
    208      *
    209      * Use in conjunction with the ms-required group.
    210      */
    211     public function skipWithoutMultisite() {
    212         if ( ! is_multisite() ) {
    213             $this->markTestSkipped( 'Test only runs on Multisite' );
    214         }
    215     }
    216 
    217     /**
    218      * Allow tests to be skipped when Multisite is in use.
    219      *
    220      * Use in conjunction with the ms-excluded group.
    221      */
    222     public function skipWithMultisite() {
    223         if ( is_multisite() ) {
    224             $this->markTestSkipped( 'Test does not run on Multisite' );
    225         }
    226     }
    227 
    228     /**
    229      * Unregister existing post types and register defaults.
    230      *
    231      * Run before each test in order to clean up the global scope, in case
    232      * a test forgets to unregister a post type on its own, or fails before
    233      * it has a chance to do so.
    234      */
    235     protected function reset_post_types() {
    236         foreach ( get_post_types( array(), 'objects' ) as $pt ) {
    237             if ( empty( $pt->tests_no_auto_unregister ) ) {
    238                 _unregister_post_type( $pt->name );
    239             }
    240         }
    241         create_initial_post_types();
    242     }
    243 
    244     /**
    245      * Unregister existing taxonomies and register defaults.
    246      *
    247      * Run before each test in order to clean up the global scope, in case
    248      * a test forgets to unregister a taxonomy on its own, or fails before
    249      * it has a chance to do so.
    250      */
    251     protected function reset_taxonomies() {
    252         foreach ( get_taxonomies() as $tax ) {
    253             _unregister_taxonomy( $tax );
    254         }
    255         create_initial_taxonomies();
    256     }
    257 
    258     /**
    259      * Unregister non-built-in post statuses.
    260      */
    261     protected function reset_post_statuses() {
    262         foreach ( get_post_stati( array( '_builtin' => false ) ) as $post_status ) {
    263             _unregister_post_status( $post_status );
    264         }
    265     }
    266 
    267     /**
    268      * Reset `$_SERVER` variables
    269      */
    270     protected function reset__SERVER() {
    271         tests_reset__SERVER();
    272     }
    273 
    274     /**
    275      * Saves the action and filter-related globals so they can be restored later.
    276      *
    277      * Stores $wp_actions, $wp_current_filter, and $wp_filter
    278      * on a class variable so they can be restored on tearDown() using _restore_hooks().
    279      *
    280      * @global array $wp_actions
    281      * @global array $wp_current_filter
    282      * @global array $wp_filter
    283      * @return void
    284      */
    285     protected function _backup_hooks() {
    286         $globals = array( 'wp_actions', 'wp_current_filter' );
    287         foreach ( $globals as $key ) {
    288             self::$hooks_saved[ $key ] = $GLOBALS[ $key ];
    289         }
    290         self::$hooks_saved['wp_filter'] = array();
    291         foreach ( $GLOBALS['wp_filter'] as $hook_name => $hook_object ) {
    292             self::$hooks_saved['wp_filter'][ $hook_name ] = clone $hook_object;
    293         }
    294     }
    295 
    296     /**
    297      * Restores the hook-related globals to their state at setUp()
    298      * so that future tests aren't affected by hooks set during this last test.
    299      *
    300      * @global array $wp_actions
    301      * @global array $wp_current_filter
    302      * @global array $wp_filter
    303      * @return void
    304      */
    305     protected function _restore_hooks() {
    306         $globals = array( 'wp_actions', 'wp_current_filter' );
    307         foreach ( $globals as $key ) {
    308             if ( isset( self::$hooks_saved[ $key ] ) ) {
    309                 $GLOBALS[ $key ] = self::$hooks_saved[ $key ];
    310             }
    311         }
    312         if ( isset( self::$hooks_saved['wp_filter'] ) ) {
    313             $GLOBALS['wp_filter'] = array();
    314             foreach ( self::$hooks_saved['wp_filter'] as $hook_name => $hook_object ) {
    315                 $GLOBALS['wp_filter'][ $hook_name ] = clone $hook_object;
    316             }
    317         }
    318     }
    319 
    320     static function flush_cache() {
    321         global $wp_object_cache;
    322         $wp_object_cache->group_ops      = array();
    323         $wp_object_cache->stats          = array();
    324         $wp_object_cache->memcache_debug = array();
    325         $wp_object_cache->cache          = array();
    326         if ( method_exists( $wp_object_cache, '__remoteset' ) ) {
    327             $wp_object_cache->__remoteset();
    328         }
    329         wp_cache_flush();
    330         wp_cache_add_global_groups( array( 'users', 'userlogins', 'usermeta', 'user_meta', 'useremail', 'userslugs', 'site-transient', 'site-options', 'blog-lookup', 'blog-details', 'rss', 'global-posts', 'blog-id-cache', 'networks', 'sites', 'site-details', 'blog_meta' ) );
    331         wp_cache_add_non_persistent_groups( array( 'comment', 'counts', 'plugins' ) );
    332     }
    333 
    334     /**
    335      * Clean up any registered meta keys.
    336      *
    337      * @since 5.1.0
    338      *
    339      * @global array $wp_meta_keys
    340      */
    341     function unregister_all_meta_keys() {
    342         global $wp_meta_keys;
    343         if ( ! is_array( $wp_meta_keys ) ) {
    344             return;
    345         }
    346         foreach ( $wp_meta_keys as $object_type => $type_keys ) {
    347             foreach ( $type_keys as $object_subtype => $subtype_keys ) {
    348                 foreach ( $subtype_keys as $key => $value ) {
    349                     unregister_meta_key( $object_type, $key, $object_subtype );
    350                 }
    351             }
    352         }
    353     }
    354 
    355     function start_transaction() {
    356         global $wpdb;
    357         $wpdb->query( 'SET autocommit = 0;' );
    358         $wpdb->query( 'START TRANSACTION;' );
    359         add_filter( 'query', array( $this, '_create_temporary_tables' ) );
    360         add_filter( 'query', array( $this, '_drop_temporary_tables' ) );
    361     }
    362 
    363     /**
    364      * Commit the queries in a transaction.
    365      *
    366      * @since 4.1.0
    367      */
    368     public static function commit_transaction() {
    369         global $wpdb;
    370         $wpdb->query( 'COMMIT;' );
    371     }
    372 
    373     function _create_temporary_tables( $query ) {
    374         if ( 'CREATE TABLE' === substr( trim( $query ), 0, 12 ) ) {
    375             return substr_replace( trim( $query ), 'CREATE TEMPORARY TABLE', 0, 12 );
    376         }
    377         return $query;
    378     }
    379 
    380     function _drop_temporary_tables( $query ) {
    381         if ( 'DROP TABLE' === substr( trim( $query ), 0, 10 ) ) {
    382             return substr_replace( trim( $query ), 'DROP TEMPORARY TABLE', 0, 10 );
    383         }
    384         return $query;
    385     }
    386 
    387     function get_wp_die_handler( $handler ) {
    388         return array( $this, 'wp_die_handler' );
    389     }
    390 
    391     function wp_die_handler( $message ) {
    392         if ( ! is_scalar( $message ) ) {
    393             $message = '0';
    394         }
    395 
    396         throw new WPDieException( $message );
    397     }
    398 
    399     function expectDeprecated() {
    400         $annotations = $this->getAnnotations();
    401         foreach ( array( 'class', 'method' ) as $depth ) {
    402             if ( ! empty( $annotations[ $depth ]['expectedDeprecated'] ) ) {
    403                 $this->expected_deprecated = array_merge( $this->expected_deprecated, $annotations[ $depth ]['expectedDeprecated'] );
    404             }
    405             if ( ! empty( $annotations[ $depth ]['expectedIncorrectUsage'] ) ) {
    406                 $this->expected_doing_it_wrong = array_merge( $this->expected_doing_it_wrong, $annotations[ $depth ]['expectedIncorrectUsage'] );
    407             }
    408         }
    409         add_action( 'deprecated_function_run', array( $this, 'deprecated_function_run' ) );
    410         add_action( 'deprecated_argument_run', array( $this, 'deprecated_function_run' ) );
    411         add_action( 'deprecated_hook_run', array( $this, 'deprecated_function_run' ) );
    412         add_action( 'doing_it_wrong_run', array( $this, 'doing_it_wrong_run' ) );
    413         add_action( 'deprecated_function_trigger_error', '__return_false' );
    414         add_action( 'deprecated_argument_trigger_error', '__return_false' );
    415         add_action( 'deprecated_hook_trigger_error', '__return_false' );
    416         add_action( 'doing_it_wrong_trigger_error', '__return_false' );
    417     }
    418 
    419     function expectedDeprecated() {
    420         $errors = array();
    421 
    422         $not_caught_deprecated = array_diff( $this->expected_deprecated, $this->caught_deprecated );
    423         foreach ( $not_caught_deprecated as $not_caught ) {
    424             $errors[] = "Failed to assert that $not_caught triggered a deprecated notice";
    425         }
    426 
    427         $unexpected_deprecated = array_diff( $this->caught_deprecated, $this->expected_deprecated );
    428         foreach ( $unexpected_deprecated as $unexpected ) {
    429             $errors[] = "Unexpected deprecated notice for $unexpected";
    430         }
    431 
    432         $not_caught_doing_it_wrong = array_diff( $this->expected_doing_it_wrong, $this->caught_doing_it_wrong );
    433         foreach ( $not_caught_doing_it_wrong as $not_caught ) {
    434             $errors[] = "Failed to assert that $not_caught triggered an incorrect usage notice";
    435         }
    436 
    437         $unexpected_doing_it_wrong = array_diff( $this->caught_doing_it_wrong, $this->expected_doing_it_wrong );
    438         foreach ( $unexpected_doing_it_wrong as $unexpected ) {
    439             $errors[] = "Unexpected incorrect usage notice for $unexpected";
    440         }
    441 
    442         // Perform an assertion, but only if there are expected or unexpected deprecated calls or wrongdoings
    443         if ( ! empty( $this->expected_deprecated ) ||
    444             ! empty( $this->expected_doing_it_wrong ) ||
    445             ! empty( $this->caught_deprecated ) ||
    446             ! empty( $this->caught_doing_it_wrong ) ) {
    447             $this->assertEmpty( $errors, implode( "\n", $errors ) );
    448         }
    449     }
    450 
    451     /**
    452      * Declare an expected `_deprecated_function()` or `_deprecated_argument()` call from within a test.
    453      *
    454      * @since 4.2.0
    455      *
    456      * @param string $deprecated Name of the function, method, class, or argument that is deprecated. Must match
    457      *                           first parameter of the `_deprecated_function()` or `_deprecated_argument()` call.
    458      */
    459     public function setExpectedDeprecated( $deprecated ) {
    460         array_push( $this->expected_deprecated, $deprecated );
    461     }
    462 
    463     /**
    464      * Declare an expected `_doing_it_wrong()` call from within a test.
    465      *
    466      * @since 4.2.0
    467      *
    468      * @param string $deprecated Name of the function, method, or class that appears in the first argument of the
    469      *                           source `_doing_it_wrong()` call.
    470      */
    471     public function setExpectedIncorrectUsage( $doing_it_wrong ) {
    472         array_push( $this->expected_doing_it_wrong, $doing_it_wrong );
    473     }
    474 
    475     /**
    476      * PHPUnit 6+ compatibility shim.
    477      *
    478      * @param mixed      $exception
    479      * @param string     $message
    480      * @param int|string $code
    481      */
    482     public function setExpectedException( $exception, $message = '', $code = null ) {
    483         if ( method_exists( 'PHPUnit_Framework_TestCase', 'setExpectedException' ) ) {
    484             parent::setExpectedException( $exception, $message, $code );
    485         } else {
    486             $this->expectException( $exception );
    487             if ( '' !== $message ) {
    488                 $this->expectExceptionMessage( $message );
    489             }
    490             if ( null !== $code ) {
    491                 $this->expectExceptionCode( $code );
    492             }
    493         }
    494     }
    495 
    496     function deprecated_function_run( $function ) {
    497         if ( ! in_array( $function, $this->caught_deprecated ) ) {
    498             $this->caught_deprecated[] = $function;
    499         }
    500     }
    501 
    502     function doing_it_wrong_run( $function ) {
    503         if ( ! in_array( $function, $this->caught_doing_it_wrong ) ) {
    504             $this->caught_doing_it_wrong[] = $function;
    505         }
    506     }
    507 
    508     function assertWPError( $actual, $message = '' ) {
    509         $this->assertInstanceOf( 'WP_Error', $actual, $message );
    510     }
    511 
    512     function assertNotWPError( $actual, $message = '' ) {
    513         if ( is_wp_error( $actual ) && '' === $message ) {
    514             $message = $actual->get_error_message();
    515         }
    516         $this->assertNotInstanceOf( 'WP_Error', $actual, $message );
    517     }
    518 
    519     function assertIXRError( $actual, $message = '' ) {
    520         $this->assertInstanceOf( 'IXR_Error', $actual, $message );
    521     }
    522 
    523     function assertNotIXRError( $actual, $message = '' ) {
    524         if ( $actual instanceof IXR_Error && '' === $message ) {
    525             $message = $actual->message;
    526         }
    527         $this->assertNotInstanceOf( 'IXR_Error', $actual, $message );
    528     }
    529 
    530     function assertEqualFields( $object, $fields ) {
    531         foreach ( $fields as $field_name => $field_value ) {
    532             if ( $object->$field_name != $field_value ) {
    533                 $this->fail();
    534             }
    535         }
    536     }
    537 
    538     function assertDiscardWhitespace( $expected, $actual ) {
    539         $this->assertEquals( preg_replace( '/\s*/', '', $expected ), preg_replace( '/\s*/', '', $actual ) );
    540     }
    541 
    542     /**
    543      * Asserts that the contents of two un-keyed, single arrays are equal, without accounting for the order of elements.
    544      *
    545      * @since 3.5.0
    546      *
    547      * @param array $expected Expected array.
    548      * @param array $actual   Array to check.
    549      */
    550     function assertEqualSets( $expected, $actual ) {
    551         sort( $expected );
    552         sort( $actual );
    553         $this->assertEquals( $expected, $actual );
    554     }
    555 
    556     /**
    557      * Asserts that the contents of two keyed, single arrays are equal, without accounting for the order of elements.
    558      *
    559      * @since 4.1.0
    560      *
    561      * @param array $expected Expected array.
    562      * @param array $actual   Array to check.
    563      */
    564     function assertEqualSetsWithIndex( $expected, $actual ) {
    565         ksort( $expected );
    566         ksort( $actual );
    567         $this->assertEquals( $expected, $actual );
    568     }
    569 
    570     /**
    571      * Asserts that the given variable is a multidimensional array, and that all arrays are non-empty.
    572      *
    573      * @since 4.8.0
    574      *
    575      * @param array $array Array to check.
    576      */
    577     function assertNonEmptyMultidimensionalArray( $array ) {
    578         $this->assertTrue( is_array( $array ) );
    579         $this->assertNotEmpty( $array );
    580 
    581         foreach ( $array as $sub_array ) {
    582             $this->assertTrue( is_array( $sub_array ) );
    583             $this->assertNotEmpty( $sub_array );
    584         }
    585     }
     14class WP_UnitTestCase extends WP_UnitTestCase_Base {
    58615
    58716    /**
     
    60130        self::assertThat( $condition, self::logicalNot( self::isFalse() ), $message );
    60231    }
    603 
    604     /**
    605      * Sets the global state to as if a given URL has been requested.
    606      *
    607      * This sets:
    608      * - The super globals.
    609      * - The globals.
    610      * - The query variables.
    611      * - The main query.
    612      *
    613      * @since 3.5.0
    614      *
    615      * @param string $url The URL for the request.
    616      */
    617     function go_to( $url ) {
    618         // note: the WP and WP_Query classes like to silently fetch parameters
    619         // from all over the place (globals, GET, etc), which makes it tricky
    620         // to run them more than once without very carefully clearing everything
    621         $_GET = $_POST = array();
    622         foreach ( array( 'query_string', 'id', 'postdata', 'authordata', 'day', 'currentmonth', 'page', 'pages', 'multipage', 'more', 'numpages', 'pagenow' ) as $v ) {
    623             if ( isset( $GLOBALS[ $v ] ) ) {
    624                 unset( $GLOBALS[ $v ] );
    625             }
    626         }
    627         $parts = parse_url( $url );
    628         if ( isset( $parts['scheme'] ) ) {
    629             $req = isset( $parts['path'] ) ? $parts['path'] : '';
    630             if ( isset( $parts['query'] ) ) {
    631                 $req .= '?' . $parts['query'];
    632                 // parse the url query vars into $_GET
    633                 parse_str( $parts['query'], $_GET );
    634             }
    635         } else {
    636             $req = $url;
    637         }
    638         if ( ! isset( $parts['query'] ) ) {
    639             $parts['query'] = '';
    640         }
    641 
    642         $_SERVER['REQUEST_URI'] = $req;
    643         unset( $_SERVER['PATH_INFO'] );
    644 
    645         self::flush_cache();
    646         unset( $GLOBALS['wp_query'], $GLOBALS['wp_the_query'] );
    647         $GLOBALS['wp_the_query'] = new WP_Query();
    648         $GLOBALS['wp_query']     = $GLOBALS['wp_the_query'];
    649 
    650         $public_query_vars  = $GLOBALS['wp']->public_query_vars;
    651         $private_query_vars = $GLOBALS['wp']->private_query_vars;
    652 
    653         $GLOBALS['wp']                     = new WP();
    654         $GLOBALS['wp']->public_query_vars  = $public_query_vars;
    655         $GLOBALS['wp']->private_query_vars = $private_query_vars;
    656 
    657         _cleanup_query_vars();
    658 
    659         $GLOBALS['wp']->main( $parts['query'] );
    660     }
    661 
    662     /**
    663      * Allows tests to be skipped on single or multisite installs by using @group annotations.
    664      *
    665      * This is a custom extension of the PHPUnit requirements handling.
    666      *
    667      * Contains legacy code for skipping tests that are associated with an open Trac ticket. Core tests no longer
    668      * support this behaviour.
    669      *
    670      * @since 3.5.0
    671      */
    672     protected function checkRequirements() {
    673         parent::checkRequirements();
    674 
    675         $annotations = $this->getAnnotations();
    676 
    677         $groups = array();
    678         if ( ! empty( $annotations['class']['group'] ) ) {
    679             $groups = array_merge( $groups, $annotations['class']['group'] );
    680         }
    681         if ( ! empty( $annotations['method']['group'] ) ) {
    682             $groups = array_merge( $groups, $annotations['method']['group'] );
    683         }
    684 
    685         if ( ! empty( $groups ) ) {
    686             if ( in_array( 'ms-required', $groups, true ) ) {
    687                 $this->skipWithoutMultisite();
    688             }
    689 
    690             if ( in_array( 'ms-excluded', $groups, true ) ) {
    691                 $this->skipWithMultisite();
    692             }
    693         }
    694 
    695         // Core tests no longer check against open Trac tickets, but others using WP_UnitTestCase may do so.
    696         if ( defined( 'WP_RUN_CORE_TESTS' ) && WP_RUN_CORE_TESTS ) {
    697             return;
    698         }
    699 
    700         if ( WP_TESTS_FORCE_KNOWN_BUGS ) {
    701             return;
    702         }
    703         $tickets = PHPUnit_Util_Test::getTickets( get_class( $this ), $this->getName( false ) );
    704         foreach ( $tickets as $ticket ) {
    705             if ( is_numeric( $ticket ) ) {
    706                 $this->knownWPBug( $ticket );
    707             } elseif ( 'Plugin' == substr( $ticket, 0, 6 ) ) {
    708                 $ticket = substr( $ticket, 6 );
    709                 if ( $ticket && is_numeric( $ticket ) ) {
    710                     $this->knownPluginBug( $ticket );
    711                 }
    712             }
    713         }
    714     }
    715 
    716     /**
    717      * Skips the current test if there is an open Trac ticket associated with it.
    718      *
    719      * @since 3.5.0
    720      *
    721      * @param int $ticket_id Ticket number.
    722      */
    723     function knownWPBug( $ticket_id ) {
    724         if ( WP_TESTS_FORCE_KNOWN_BUGS || in_array( $ticket_id, self::$forced_tickets ) ) {
    725             return;
    726         }
    727         if ( ! TracTickets::isTracTicketClosed( 'https://core.trac.wordpress.org', $ticket_id ) ) {
    728             $this->markTestSkipped( sprintf( 'WordPress Ticket #%d is not fixed', $ticket_id ) );
    729         }
    730     }
    731 
    732     /**
    733      * Skips the current test if there is an open Unit Test Trac ticket associated with it.
    734      *
    735      * @since 3.5.0
    736      *
    737      * @deprecated No longer used since the Unit Test Trac was merged into the Core Trac.
    738      *
    739      * @param int $ticket_id Ticket number.
    740      */
    741     function knownUTBug( $ticket_id ) {
    742         return;
    743     }
    744 
    745     /**
    746      * Skips the current test if there is an open Plugin Trac ticket associated with it.
    747      *
    748      * @since 3.5.0
    749      *
    750      * @param int $ticket_id Ticket number.
    751      */
    752     function knownPluginBug( $ticket_id ) {
    753         if ( WP_TESTS_FORCE_KNOWN_BUGS || in_array( 'Plugin' . $ticket_id, self::$forced_tickets ) ) {
    754             return;
    755         }
    756         if ( ! TracTickets::isTracTicketClosed( 'https://plugins.trac.wordpress.org', $ticket_id ) ) {
    757             $this->markTestSkipped( sprintf( 'WordPress Plugin Ticket #%d is not fixed', $ticket_id ) );
    758         }
    759     }
    760 
    761     /**
    762      * Adds a Trac ticket number to the `$forced_tickets` property.
    763      *
    764      * @since 3.5.0
    765      *
    766      * @param int $ticket Ticket number.
    767      */
    768     public static function forceTicket( $ticket ) {
    769         self::$forced_tickets[] = $ticket;
    770     }
    771 
    772     /**
    773      * Custom preparations for the PHPUnit process isolation template.
    774      *
    775      * When restoring global state between tests, PHPUnit defines all the constants that were already defined, and then
    776      * includes included files. This does not work with WordPress, as the included files define the constants.
    777      *
    778      * This method defines the constants after including files.
    779      *
    780      * @param Text_Template $template
    781      */
    782     function prepareTemplate( Text_Template $template ) {
    783         $template->setVar( array( 'constants' => '' ) );
    784         $template->setVar( array( 'wp_constants' => PHPUnit_Util_GlobalState::getConstantsAsString() ) );
    785         parent::prepareTemplate( $template );
    786     }
    787 
    788     /**
    789      * Creates a unique temporary file name.
    790      *
    791      * The directory in which the file is created depends on the environment configuration.
    792      *
    793      * @since 3.5.0
    794      *
    795      * @return string|bool Path on success, else false.
    796      */
    797     function temp_filename() {
    798         $tmp_dir = '';
    799         $dirs    = array( 'TMP', 'TMPDIR', 'TEMP' );
    800         foreach ( $dirs as $dir ) {
    801             if ( isset( $_ENV[ $dir ] ) && ! empty( $_ENV[ $dir ] ) ) {
    802                 $tmp_dir = $dir;
    803                 break;
    804             }
    805         }
    806         if ( empty( $tmp_dir ) ) {
    807             $tmp_dir = '/tmp';
    808         }
    809         $tmp_dir = realpath( $tmp_dir );
    810         return tempnam( $tmp_dir, 'wpunit' );
    811     }
    812 
    813     /**
    814      * Checks each of the WP_Query is_* functions/properties against expected boolean value.
    815      *
    816      * Any properties that are listed by name as parameters will be expected to be true; all others are
    817      * expected to be false. For example, assertQueryTrue('is_single', 'is_feed') means is_single()
    818      * and is_feed() must be true and everything else must be false to pass.
    819      *
    820      * @since 2.5.0
    821      * @since 3.8.0 Moved from `Tests_Query_Conditionals` to `WP_UnitTestCase`.
    822      *
    823      * @param string $prop,... Any number of WP_Query properties that are expected to be true for the current request.
    824      */
    825     function assertQueryTrue() {
    826         global $wp_query;
    827         $all  = array(
    828             'is_404',
    829             'is_admin',
    830             'is_archive',
    831             'is_attachment',
    832             'is_author',
    833             'is_category',
    834             'is_comment_feed',
    835             'is_date',
    836             'is_day',
    837             'is_embed',
    838             'is_feed',
    839             'is_front_page',
    840             'is_home',
    841             'is_month',
    842             'is_page',
    843             'is_paged',
    844             'is_post_type_archive',
    845             'is_posts_page',
    846             'is_preview',
    847             'is_robots',
    848             'is_search',
    849             'is_single',
    850             'is_singular',
    851             'is_tag',
    852             'is_tax',
    853             'is_time',
    854             'is_trackback',
    855             'is_year',
    856         );
    857         $true = func_get_args();
    858 
    859         foreach ( $true as $true_thing ) {
    860             $this->assertContains( $true_thing, $all, "Unknown conditional: {$true_thing}." );
    861         }
    862 
    863         $passed  = true;
    864         $message = '';
    865 
    866         foreach ( $all as $query_thing ) {
    867             $result = is_callable( $query_thing ) ? call_user_func( $query_thing ) : $wp_query->$query_thing;
    868 
    869             if ( in_array( $query_thing, $true ) ) {
    870                 if ( ! $result ) {
    871                     $message .= $query_thing . ' is false but is expected to be true. ' . PHP_EOL;
    872                     $passed   = false;
    873                 }
    874             } elseif ( $result ) {
    875                 $message .= $query_thing . ' is true but is expected to be false. ' . PHP_EOL;
    876                 $passed   = false;
    877             }
    878         }
    879 
    880         if ( ! $passed ) {
    881             $this->fail( $message );
    882         }
    883     }
    884 
    885     /**
    886      * Selectively deletes a file.
    887      *
    888      * Does not delete a file if its path is set in the `$ignore_files` property.
    889      *
    890      * @param string $file File path.
    891      */
    892     function unlink( $file ) {
    893         $exists = is_file( $file );
    894         if ( $exists && ! in_array( $file, self::$ignore_files ) ) {
    895             //error_log( $file );
    896             unlink( $file );
    897         } elseif ( ! $exists ) {
    898             $this->fail( "Trying to delete a file that doesn't exist: $file" );
    899         }
    900     }
    901 
    902     /**
    903      * Selectively deletes files from a directory.
    904      *
    905      * Does not delete files if their paths are set in the `$ignore_files` property.
    906      *
    907      * @param string $path Directory path.
    908      */
    909     function rmdir( $path ) {
    910         $files = $this->files_in_dir( $path );
    911         foreach ( $files as $file ) {
    912             if ( ! in_array( $file, self::$ignore_files ) ) {
    913                 $this->unlink( $file );
    914             }
    915         }
    916     }
    917 
    918     /**
    919      * Deletes files added to the `uploads` directory during tests.
    920      *
    921      * This method works in tandem with the `setUp()` and `rmdir()` methods:
    922      * - `setUp()` scans the `uploads` directory before every test, and stores its contents inside of the
    923      *   `$ignore_files` property.
    924      * - `rmdir()` and its helper methods only delete files that are not listed in the `$ignore_files` property. If
    925      *   called during `tearDown()` in tests, this will only delete files added during the previously run test.
    926      */
    927     function remove_added_uploads() {
    928         $uploads = wp_upload_dir();
    929         $this->rmdir( $uploads['basedir'] );
    930     }
    931 
    932     /**
    933      * Returns a list of all files contained inside a directory.
    934      *
    935      * @since 4.0.0
    936      *
    937      * @param string $dir Path to the directory to scan.
    938      *
    939      * @return array List of file paths.
    940      */
    941     function files_in_dir( $dir ) {
    942         $files = array();
    943 
    944         $iterator = new RecursiveDirectoryIterator( $dir );
    945         $objects  = new RecursiveIteratorIterator( $iterator );
    946         foreach ( $objects as $name => $object ) {
    947             if ( is_file( $name ) ) {
    948                 $files[] = $name;
    949             }
    950         }
    951 
    952         return $files;
    953     }
    954 
    955     /**
    956      * Returns a list of all files contained inside the `uploads` directory.
    957      *
    958      * @since 4.0.0
    959      *
    960      * @return array List of file paths.
    961      */
    962     function scan_user_uploads() {
    963         static $files = array();
    964         if ( ! empty( $files ) ) {
    965             return $files;
    966         }
    967 
    968         $uploads = wp_upload_dir();
    969         $files   = $this->files_in_dir( $uploads['basedir'] );
    970         return $files;
    971     }
    972 
    973     /**
    974      * Deletes all directories contained inside a directory.
    975      *
    976      * @since 4.1.0
    977      *
    978      * @param string $path Path to the directory to scan.
    979      */
    980     function delete_folders( $path ) {
    981         $this->matched_dirs = array();
    982         if ( ! is_dir( $path ) ) {
    983             return;
    984         }
    985 
    986         $this->scandir( $path );
    987         foreach ( array_reverse( $this->matched_dirs ) as $dir ) {
    988             rmdir( $dir );
    989         }
    990         rmdir( $path );
    991     }
    992 
    993     /**
    994      * Retrieves all directories contained inside a directory and stores them in the `$matched_dirs` property. Hidden
    995      * directories are ignored.
    996      *
    997      * This is a helper for the `delete_folders()` method.
    998      *
    999      * @since 4.1.0
    1000      *
    1001      * @param string $dir Path to the directory to scan.
    1002      */
    1003     function scandir( $dir ) {
    1004         foreach ( scandir( $dir ) as $path ) {
    1005             if ( 0 !== strpos( $path, '.' ) && is_dir( $dir . '/' . $path ) ) {
    1006                 $this->matched_dirs[] = $dir . '/' . $path;
    1007                 $this->scandir( $dir . '/' . $path );
    1008             }
    1009         }
    1010     }
    1011 
    1012     /**
    1013      * Converts a microtime string into a float.
    1014      *
    1015      * @since 4.1.0
    1016      *
    1017      * @param string $microtime Time string generated by `microtime()`.
    1018      *
    1019      * @return float `microtime()` output as a float.
    1020      */
    1021     protected function _microtime_to_float( $microtime ) {
    1022         $time_array = explode( ' ', $microtime );
    1023         return array_sum( $time_array );
    1024     }
    1025 
    1026     /**
    1027      * Deletes a user from the database in a Multisite-agnostic way.
    1028      *
    1029      * @since 4.3.0
    1030      *
    1031      * @param int $user_id User ID.
    1032      *
    1033      * @return bool True if the user was deleted.
    1034      */
    1035     public static function delete_user( $user_id ) {
    1036         if ( is_multisite() ) {
    1037             return wpmu_delete_user( $user_id );
    1038         } else {
    1039             return wp_delete_user( $user_id );
    1040         }
    1041     }
    1042 
    1043     /**
    1044      * Resets permalinks and flushes rewrites.
    1045      *
    1046      * @since 4.4.0
    1047      *
    1048      * @global WP_Rewrite $wp_rewrite
    1049      *
    1050      * @param string $structure Optional. Permalink structure to set. Default empty.
    1051      */
    1052     public function set_permalink_structure( $structure = '' ) {
    1053         global $wp_rewrite;
    1054 
    1055         $wp_rewrite->init();
    1056         $wp_rewrite->set_permalink_structure( $structure );
    1057         $wp_rewrite->flush_rules();
    1058     }
    1059 
    1060     /**
    1061      * Creates an attachment post from an uploaded file.
    1062      *
    1063      * @since 4.4.0
    1064      *
    1065      * @param array $upload         Array of information about the uploaded file, provided by wp_upload_bits().
    1066      * @param int   $parent_post_id Optional. Parent post ID.
    1067      *
    1068      * @return int|WP_Error The attachment ID on success. The value 0 or WP_Error on failure.
    1069      */
    1070     function _make_attachment( $upload, $parent_post_id = 0 ) {
    1071         $type = '';
    1072         if ( ! empty( $upload['type'] ) ) {
    1073             $type = $upload['type'];
    1074         } else {
    1075             $mime = wp_check_filetype( $upload['file'] );
    1076             if ( $mime ) {
    1077                 $type = $mime['type'];
    1078             }
    1079         }
    1080 
    1081         $attachment = array(
    1082             'post_title'     => basename( $upload['file'] ),
    1083             'post_content'   => '',
    1084             'post_type'      => 'attachment',
    1085             'post_parent'    => $parent_post_id,
    1086             'post_mime_type' => $type,
    1087             'guid'           => $upload['url'],
    1088         );
    1089 
    1090         $id = wp_insert_attachment( $attachment, $upload['file'], $parent_post_id );
    1091         wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $upload['file'] ) );
    1092         return $id;
    1093     }
    1094 
    1095     /**
    1096      * Updates the modified and modified GMT date of a post in the database.
    1097      *
    1098      * @since 4.8.0
    1099      *
    1100      * @global wpdb $wpdb WordPress database abstraction object.
    1101      *
    1102      * @param int    $post_id Post ID.
    1103      * @param string $date    Post date, in the format YYYY-MM-DD HH:MM:SS.
    1104      *
    1105      * @return int|false 1 on success, or false on error.
    1106      */
    1107     protected function update_post_modified( $post_id, $date ) {
    1108         global $wpdb;
    1109         return $wpdb->update(
    1110             $wpdb->posts,
    1111             array(
    1112                 'post_modified'     => $date,
    1113                 'post_modified_gmt' => $date,
    1114             ),
    1115             array(
    1116                 'ID' => $post_id,
    1117             ),
    1118             array(
    1119                 '%s',
    1120                 '%s',
    1121             ),
    1122             array(
    1123                 '%d',
    1124             )
    1125         );
    1126     }
    112732}
  • trunk/tests/phpunit/multisite.xml

    r44128 r44701  
    4646    </php>
    4747    <listeners>
    48         <listener class="SpeedTrapListener" file="tests/phpunit/includes/speed-trap-listener.php">
     48        <listener class="SpeedTrapListener" file="tests/phpunit/includes/listener-loader.php">
    4949            <arguments>
    5050                <array>
  • trunk/tests/phpunit/tests/post/query.php

    r44456 r44701  
    720720        $q->posts = $posts;
    721721
    722         $methd = new \ReflectionMethod( 'WP_Query', 'set_found_posts' );
     722        $methd = new ReflectionMethod( 'WP_Query', 'set_found_posts' );
    723723        $methd->setAccessible( true );
    724724        $methd->invoke( $q, array( 'no_found_rows' => false ), array() );
Note: See TracChangeset for help on using the changeset viewer.