Mini Shell
<?php defined('PHPREDIS_TESTRUN') or die("Use TestRedis.php to run tests!\n");
require_once(dirname($_SERVER['PHP_SELF'])."/TestSuite.php");
define('REDIS_ARRAY_DATA_SIZE', 1000);
function custom_hash($str) {
// str has the following format: $APPID_fb$FACEBOOKID_$key.
$pos = strpos($str, '_fb');
if(preg_match("#\w+_fb(?<facebook_id>\d+)_\w+#", $str, $out)) {
return $out['facebook_id'];
}
return $str;
}
function parseHostPort($str, &$host, &$port) {
$pos = strrpos($str, ':');
$host = substr($str, 0, $pos);
$port = substr($str, $pos+1);
}
function getRedisVersion($obj_r) {
$arr_info = $obj_r->info();
if (!$arr_info || !isset($arr_info['redis_version'])) {
return "0.0.0";
}
return $arr_info['redis_version'];
}
/* Determine the lowest redis version attached to this RedisArray object */
function getMinVersion($obj_ra) {
$min_version = "0.0.0";
foreach ($obj_ra->_hosts() as $host) {
$version = getRedisVersion($obj_ra->_instance($host));
if (version_compare($version, $min_version) > 0) {
$min_version = $version;
}
}
return $min_version;
}
class Redis_Array_Test extends TestSuite
{
private $min_version;
private $strings;
public $ra = NULL;
private $data = NULL;
public function setUp() {
// initialize strings.
$n = REDIS_ARRAY_DATA_SIZE;
$this->strings = array();
for($i = 0; $i < $n; $i++) {
$this->strings['key-'.$i] = 'val-'.$i;
}
global $newRing, $oldRing, $useIndex;
$this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex));
$this->min_version = getMinVersion($this->ra);
}
public function testMSet() {
// run mset
$this->assertTrue(TRUE === $this->ra->mset($this->strings));
// check each key individually using the array
foreach($this->strings as $k => $v) {
$this->assertTrue($v === $this->ra->get($k));
}
// check each key individually using a new connection
foreach($this->strings as $k => $v) {
parseHostPort($this->ra->_target($k), $host, $port);
$target = $this->ra->_target($k);
$pos = strrpos($target, ':');
$host = substr($target, 0, $pos);
$port = substr($target, $pos+1);
$r = new Redis;
$r->pconnect($host, (int)$port);
$this->assertTrue($v === $r->get($k));
}
}
public function testMGet() {
$this->assertTrue(array_values($this->strings) === $this->ra->mget(array_keys($this->strings)));
}
private function addData($commonString) {
$this->data = array();
for($i = 0; $i < REDIS_ARRAY_DATA_SIZE; $i++) {
$k = rand().'_'.$commonString.'_'.rand();
$this->data[$k] = rand();
}
$this->ra->mset($this->data);
}
private function checkCommonLocality() {
// check that they're all on the same node.
$lastNode = NULL;
foreach($this->data as $k => $v) {
$node = $this->ra->_target($k);
if($lastNode) {
$this->assertTrue($node === $lastNode);
}
$this->assertTrue($this->ra->get($k) == $v);
$lastNode = $node;
}
}
public function testKeyLocality() {
// basic key locality with default hash
$this->addData('{hashed part of the key}');
$this->checkCommonLocality();
// with common hashing function
global $newRing, $oldRing, $useIndex;
$this->ra = new RedisArray($newRing, array('previous' => $oldRing,
'index' => $useIndex,
'function' => 'custom_hash'));
// basic key locality with custom hash
$this->addData('fb'.rand());
$this->checkCommonLocality();
}
public function customDistributor($key)
{
$a = unpack("N*", md5($key, true));
global $newRing;
$pos = abs($a[1]) % count($newRing);
return $pos;
}
public function testKeyDistributor()
{
global $newRing, $useIndex;
$this->ra = new RedisArray($newRing, array(
'index' => $useIndex,
'function' => 'custom_hash',
'distributor' => array($this, "customDistributor")));
// custom key distribution function.
$this->addData('fb'.rand());
// check that they're all on the expected node.
$lastNode = NULL;
foreach($this->data as $k => $v) {
$node = $this->ra->_target($k);
$pos = $this->customDistributor($k);
$this->assertTrue($node === $newRing[$pos]);
}
}
}
class Redis_Rehashing_Test extends TestSuite
{
public $ra = NULL;
private $useIndex;
private $min_version;
// data
private $strings;
private $sets;
private $lists;
private $hashes;
private $zsets;
public function setUp() {
// initialize strings.
$n = REDIS_ARRAY_DATA_SIZE;
$this->strings = array();
for($i = 0; $i < $n; $i++) {
$this->strings['key-'.$i] = 'val-'.$i;
}
// initialize sets
for($i = 0; $i < $n; $i++) {
// each set has 20 elements
$this->sets['set-'.$i] = range($i, $i+20);
}
// initialize lists
for($i = 0; $i < $n; $i++) {
// each list has 20 elements
$this->lists['list-'.$i] = range($i, $i+20);
}
// initialize hashes
for($i = 0; $i < $n; $i++) {
// each hash has 5 keys
$this->hashes['hash-'.$i] = array('A' => $i, 'B' => $i+1, 'C' => $i+2, 'D' => $i+3, 'E' => $i+4);
}
// initialize sorted sets
for($i = 0; $i < $n; $i++) {
// each sorted sets has 5 elements
$this->zsets['zset-'.$i] = array($i, 'A', $i+1, 'B', $i+2, 'C', $i+3, 'D', $i+4, 'E');
}
global $newRing, $oldRing, $useIndex;
// create array
$this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex));
$this->min_version = getMinVersion($this->ra);
}
public function testFlush() {
// flush all servers first.
global $serverList;
foreach($serverList as $s) {
parseHostPort($s, $host, $port);
$r = new Redis();
$r->pconnect($host, (int)$port, 0);
$r->flushdb();
}
}
private function distributeKeys() {
// strings
foreach($this->strings as $k => $v) {
$this->ra->set($k, $v);
}
// sets
foreach($this->sets as $k => $v) {
call_user_func_array(array($this->ra, 'sadd'), array_merge(array($k), $v));
}
// lists
foreach($this->lists as $k => $v) {
call_user_func_array(array($this->ra, 'rpush'), array_merge(array($k), $v));
}
// hashes
foreach($this->hashes as $k => $v) {
$this->ra->hmset($k, $v);
}
// sorted sets
foreach($this->zsets as $k => $v) {
call_user_func_array(array($this->ra, 'zadd'), array_merge(array($k), $v));
}
}
public function testDistribution() {
$this->distributeKeys();
}
public function testSimpleRead() {
$this->readAllvalues();
}
private function readAllvalues() {
// strings
foreach($this->strings as $k => $v) {
$this->assertTrue($this->ra->get($k) === $v);
}
// sets
foreach($this->sets as $k => $v) {
$ret = $this->ra->smembers($k); // get values
// sort sets
sort($v);
sort($ret);
$this->assertTrue($ret == $v);
}
// lists
foreach($this->lists as $k => $v) {
$ret = $this->ra->lrange($k, 0, -1);
$this->assertTrue($ret == $v);
}
// hashes
foreach($this->hashes as $k => $v) {
$ret = $this->ra->hgetall($k); // get values
$this->assertTrue($ret == $v);
}
// sorted sets
foreach($this->zsets as $k => $v) {
$ret = $this->ra->zrange($k, 0, -1, TRUE); // get values with scores
// create assoc array from local dataset
$tmp = array();
for($i = 0; $i < count($v); $i += 2) {
$tmp[$v[$i+1]] = $v[$i];
}
// compare to RA value
$this->assertTrue($ret == $tmp);
}
}
// add a new node.
public function testCreateSecondRing() {
global $newRing, $oldRing, $serverList;
$oldRing = $newRing; // back up the original.
$newRing = $serverList; // add a new node to the main ring.
}
public function testReadUsingFallbackMechanism() {
$this->readAllvalues(); // some of the reads will fail and will go to another target node.
}
public function testRehash() {
$this->ra->_rehash(); // this will redistribute the keys
}
public function testRehashWithCallback() {
$total = 0;
$this->ra->_rehash(function ($host, $count) use (&$total) {
$total += $count;
});
$this->assertTrue($total > 0);
}
public function testReadRedistributedKeys() {
$this->readAllvalues(); // we shouldn't have any missed reads now.
}
}
// Test auto-migration of keys
class Redis_Auto_Rehashing_Test extends TestSuite {
public $ra = NULL;
private $min_version;
// data
private $strings;
public function setUp() {
// initialize strings.
$n = REDIS_ARRAY_DATA_SIZE;
$this->strings = array();
for($i = 0; $i < $n; $i++) {
$this->strings['key-'.$i] = 'val-'.$i;
}
global $newRing, $oldRing, $useIndex;
// create array
$this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex, 'autorehash' => TRUE));
$this->min_version = getMinVersion($this->ra);
}
public function testDistribute() {
// strings
foreach($this->strings as $k => $v) {
$this->ra->set($k, $v);
}
}
private function readAllvalues() {
foreach($this->strings as $k => $v) {
$this->assertTrue($this->ra->get($k) === $v);
}
}
public function testReadAll() {
$this->readAllvalues();
}
// add a new node.
public function testCreateSecondRing() {
global $newRing, $oldRing, $serverList;
$oldRing = $newRing; // back up the original.
$newRing = $serverList; // add a new node to the main ring.
}
// Read and migrate keys on fallback, causing the whole ring to be rehashed.
public function testReadAndMigrateAll() {
$this->readAllvalues();
}
// Read and migrate keys on fallback, causing the whole ring to be rehashed.
public function testAllKeysHaveBeenMigrated() {
foreach($this->strings as $k => $v) {
parseHostPort($this->ra->_target($k), $host, $port);
$r = new Redis;
$r->pconnect($host, $port);
$this->assertTrue($v === $r->get($k)); // check that the key has actually been migrated to the new node.
}
}
}
// Test node-specific multi/exec
class Redis_Multi_Exec_Test extends TestSuite {
public $ra = NULL;
private $min_version;
public function setUp() {
global $newRing, $oldRing, $useIndex;
// create array
$this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex));
$this->min_version = getMinVersion($this->ra);
}
public function testInit() {
$this->ra->set('{groups}:managers', 2);
$this->ra->set('{groups}:executives', 3);
$this->ra->set('1_{employee:joe}_name', 'joe');
$this->ra->set('1_{employee:joe}_group', 2);
$this->ra->set('1_{employee:joe}_salary', 2000);
}
public function testKeyDistribution() {
// check that all of joe's keys are on the same instance
$lastNode = NULL;
foreach(array('name', 'group', 'salary') as $field) {
$node = $this->ra->_target('1_{employee:joe}_'.$field);
if($lastNode) {
$this->assertTrue($node === $lastNode);
}
$lastNode = $node;
}
}
public function testMultiExec() {
// Joe gets a promotion
$newGroup = $this->ra->get('{groups}:executives');
$newSalary = 4000;
// change both in a transaction.
$host = $this->ra->_target('{employee:joe}'); // transactions are per-node, so we need a reference to it.
$tr = $this->ra->multi($host)
->set('1_{employee:joe}_group', $newGroup)
->set('1_{employee:joe}_salary', $newSalary)
->exec();
// check that the group and salary have been changed
$this->assertTrue($this->ra->get('1_{employee:joe}_group') === $newGroup);
$this->assertTrue($this->ra->get('1_{employee:joe}_salary') == $newSalary);
}
public function testMultiExecMSet() {
global $newGroup, $newSalary;
$newGroup = 1;
$newSalary = 10000;
// test MSET, making Joe a top-level executive
$out = $this->ra->multi($this->ra->_target('{employee:joe}'))
->mset(array('1_{employee:joe}_group' => $newGroup, '1_{employee:joe}_salary' => $newSalary))
->exec();
$this->assertTrue($out[0] === TRUE);
}
public function testMultiExecMGet() {
global $newGroup, $newSalary;
// test MGET
$out = $this->ra->multi($this->ra->_target('{employee:joe}'))
->mget(array('1_{employee:joe}_group', '1_{employee:joe}_salary'))
->exec();
$this->assertTrue($out[0][0] == $newGroup);
$this->assertTrue($out[0][1] == $newSalary);
}
public function testMultiExecDel() {
// test DEL
$out = $this->ra->multi($this->ra->_target('{employee:joe}'))
->del('1_{employee:joe}_group', '1_{employee:joe}_salary')
->exec();
$this->assertTrue($out[0] === 2);
$this->assertEquals(0, $this->ra->exists('1_{employee:joe}_group'));
$this->assertEquals(0, $this->ra->exists('1_{employee:joe}_salary'));
}
public function testMutliExecUnlink() {
if (version_compare($this->min_version, "4.0.0", "lt")) {
$this->markTestSkipped();
}
$this->ra->set('{unlink}:key1', 'bar');
$this->ra->set('{unlink}:key2', 'bar');
$out = $this->ra->multi($this->ra->_target('{unlink}'))
->del('{unlink}:key1', '{unlink}:key2')
->exec();
$this->assertTrue($out[0] === 2);
}
public function testDiscard() {
/* phpredis issue #87 */
$key = 'test_err';
$this->assertTrue($this->ra->set($key, 'test'));
$this->assertTrue('test' === $this->ra->get($key));
$this->ra->watch($key);
// After watch, same
$this->assertTrue('test' === $this->ra->get($key));
// change in a multi/exec block.
$ret = $this->ra->multi($this->ra->_target($key))->set($key, 'test1')->exec();
$this->assertTrue($ret === array(true));
// Get after exec, 'test1':
$this->assertTrue($this->ra->get($key) === 'test1');
$this->ra->watch($key);
// After second watch, still test1.
$this->assertTrue($this->ra->get($key) === 'test1');
$ret = $this->ra->multi($this->ra->_target($key))->set($key, 'test2')->discard();
// Ret after discard: NULL";
$this->assertTrue($ret === NULL);
// Get after discard, unchanged:
$this->assertTrue($this->ra->get($key) === 'test1');
}
}
// Test custom distribution function
class Redis_Distributor_Test extends TestSuite {
public $ra = NULL;
private $min_version;
public function setUp() {
global $newRing, $oldRing, $useIndex;
// create array
$this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex, 'distributor' => array($this, 'distribute')));
$this->min_version = getMinVersion($this->ra);
}
public function testInit() {
$this->ra->set('{uk}test', 'joe');
$this->ra->set('{us}test', 'bob');
}
public function distribute($key) {
$matches = array();
if (preg_match('/{([^}]+)}.*/', $key, $matches) == 1) {
$countries = array('uk' => 0, 'us' => 1);
if (array_key_exists($matches[1], $countries)) {
return $countries[$matches[1]];
}
}
return 2; // default server
}
public function testDistribution() {
$ukServer = $this->ra->_target('{uk}test');
$usServer = $this->ra->_target('{us}test');
$deServer = $this->ra->_target('{de}test');
$defaultServer = $this->ra->_target('unknown');
$nodes = $this->ra->_hosts();
$this->assertTrue($ukServer === $nodes[0]);
$this->assertTrue($usServer === $nodes[1]);
$this->assertTrue($deServer === $nodes[2]);
$this->assertTrue($defaultServer === $nodes[2]);
}
}
function run_tests($className, $str_filter, $str_host) {
// reset rings
global $newRing, $oldRing, $serverList;
$newRing = Array("$str_host:6379", "$str_host:6380", "$str_host:6381");
$oldRing = Array();
$serverList = Array("$str_host:6379", "$str_host:6380", "$str_host:6381", "$str_host:6382");
// run
return TestSuite::run($className, $str_filter);
}
?>
Zerion Mini Shell 1.0