Skip to content

Instantly share code, notes, and snippets.

@tellyworth
Created August 30, 2015 10:15
Show Gist options
  • Save tellyworth/9b37e852c34ed9fc4197 to your computer and use it in GitHub Desktop.
Save tellyworth/9b37e852c34ed9fc4197 to your computer and use it in GitHub Desktop.
<?php
/**
* @group option
* @ticket 31245
*/
class Tests_Option_Alloptions extends WP_UnitTestCase {
/**
* Some cache implementations have a 1mb limit per-key. This shouldn't be an issue when get_multi() is in use, but this provides prior coverage.
*/
function test_alloptions_1mb() {
$big_val = str_repeat( 'a', 1024*1024+1 ); // make sure we exceed 1mb
wp_cache_flush();
update_option( __FUNCTION__, $big_val, true ); // autoload = true
wp_cache_flush();
wp_load_alloptions();
global $wpdb;
$before = $wpdb->num_queries;
$value = get_option( __FUNCTION__ );
$after = $wpdb->num_queries;
$this->assertEquals( $before, $after );
$this->assertEquals( $value, $big_val ); // cached
// clean up
delete_option( __FUNCTION__ );
}
/**
* Make sure wp_load_alloptions() correctly autoloads options such that get_option() does not hit the db.
*/
function test_load_alloptions() {
global $wpdb;
$autoload_keys = $wpdb->get_col( $wpdb->prepare( "SELECT option_name FROM $wpdb->options WHERE autoload = %s", 'yes' ) );
wp_cache_flush();
wp_load_alloptions();
$before = $wpdb->num_queries;
// Fetch all of the autoload options one by one
foreach( $autoload_keys as $key ) {
$value = get_option( $key );
$this->assertNotSame( $value, false );
}
// Should have resulted in zero queries
$after = $wpdb->num_queries;
$this->assertEquals( $before, $after );
}
/**
* Make sure wp_load_alloptions() only fetches autoload=yes.
*/
function test_load_not_alloptions() {
global $wpdb;
$not_autoload_keys = $wpdb->get_col( $wpdb->prepare( "SELECT option_name FROM $wpdb->options WHERE autoload = %s", 'no' ) );
wp_cache_flush();
wp_load_alloptions();
$before = $wpdb->num_queries;
// Fetch all of the autoload options one by one
foreach( $not_autoload_keys as $key ) {
$value = get_option( $key );
$this->assertNotSame( $value, false );
}
// Should have resulted in zero queries
$after = $wpdb->num_queries;
$this->assertEquals( $before + count($not_autoload_keys), $after );
}
/**
* Simulate a race condition like those described in #31245 and #25623
* This test requires a memcached plugin/dropin; assuming one similar to Boren's plugin, hence:
* @requires function WP_Object_Cache::get_mc
*/
function test_autoload_concurrency_failure() {
// A separate object cache instance with a separate local cache array, to simulate a second process.
$other_process_cache = new WP_Object_Cache();
$value = 'Original value.';
update_option( __FUNCTION__, $value , true ); // autoload = true
wp_load_alloptions();
// Simulate a second process changing the option in db and memcache.
$value2 = 'Changed in second process.';
global $wp_object_cache;
$old_wp_object_cache = $wp_object_cache;
$wp_object_cache = $other_process_cache;
// In the second "process", change the option value, and make sure it stuck.
wp_load_alloptions();
update_option( __FUNCTION__, $value2, true);
$this->assertEquals( $value2, get_option( __FUNCTION__) );
$wp_object_cache = $old_wp_object_cache;
// Now we change a different autoload option. This will clobber the first option in cache and db.
update_option( __FUNCTION__ . '_2', 'An entirely separate option.', true);
// If not for the clobbering bug, this would correctly refresh from memcache/db.
global $wp_object_cache;
$wp_object_cache->cache = array();
$actual = get_option( __FUNCTION__ );
$this->assertEquals( $value2, $actual );
delete_option( __FUNCTION__ );
delete_option( __FUNCTION__ . '_2' );
}
/**
* Similar to test_autoload_concurrency_failure but without the clobbering update.
* This is a negative test, just to make sure the previous test is successfully simulating the race condition.
* @requires function WP_Object_Cache::get_mc
*/
function test_autoload_concurrency_pass() {
// A separate object cache instance with a separate local cache array, to simulate a second process.
$other_process_cache = new WP_Object_Cache();
$value = 'Original value.';
update_option( __FUNCTION__, $value , true ); // autoload = true
wp_load_alloptions();
// Simulate a second process changing the option in db and memcache.
$value2 = 'Changed in second process.';
global $wp_object_cache;
$old_wp_object_cache = $wp_object_cache;
$wp_object_cache = $other_process_cache;
// In the second "process", change the option value, and make sure it stuck.
wp_load_alloptions();
update_option( __FUNCTION__, $value2, true);
$this->assertEquals( $value2, get_option( __FUNCTION__) );
$wp_object_cache = $old_wp_object_cache;
// DON'T clobber the cache with an update_option() call this time.
// If not for the clobbering bug, this would correctly refresh from memcache/db.
global $wp_object_cache;
$wp_object_cache->cache = array();
$actual = get_option( __FUNCTION__ );
$this->assertEquals( $value2, $actual );
delete_option( __FUNCTION__ );
delete_option( __FUNCTION__ . '_2' );
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment