PageRenderTime 41ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/tests/array-tests.php

http://github.com/nicolasff/phpredis
PHP | 525 lines | 337 code | 123 blank | 65 comment | 20 complexity | 95b23c597e8527c8187e3a5810f37775 MD5 | raw file
Possible License(s): BSD-3-Clause, MPL-2.0-no-copyleft-exception
  1. <?php
  2. require_once(dirname($_SERVER['PHP_SELF'])."/test.php");
  3. echo "Redis Array tests.\n\n";
  4. function custom_hash($str) {
  5. // str has the following format: $APPID_fb$FACEBOOKID_$key.
  6. $pos = strpos($str, '_fb');
  7. if(preg_match("#\w+_fb(?<facebook_id>\d+)_\w+#", $str, $out)) {
  8. return $out['facebook_id'];
  9. }
  10. return $str;
  11. }
  12. class Redis_Array_Test extends TestSuite
  13. {
  14. private $strings;
  15. public $ra = NULL;
  16. private $data = NULL;
  17. public function setUp() {
  18. // initialize strings.
  19. $n = REDIS_ARRAY_DATA_SIZE;
  20. $this->strings = array();
  21. for($i = 0; $i < $n; $i++) {
  22. $this->strings['key-'.$i] = 'val-'.$i;
  23. }
  24. global $newRing, $oldRing, $useIndex;
  25. $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex));
  26. }
  27. public function testMSet() {
  28. // run mset
  29. $this->assertTrue(TRUE === $this->ra->mset($this->strings));
  30. // check each key individually using the array
  31. foreach($this->strings as $k => $v) {
  32. $this->assertTrue($v === $this->ra->get($k));
  33. }
  34. // check each key individually using a new connection
  35. foreach($this->strings as $k => $v) {
  36. list($host, $port) = split(':', $this->ra->_target($k));
  37. $r = new Redis;
  38. $r->pconnect($host, (int)$port);
  39. $this->assertTrue($v === $r->get($k));
  40. }
  41. }
  42. public function testMGet() {
  43. $this->assertTrue(array_values($this->strings) === $this->ra->mget(array_keys($this->strings)));
  44. }
  45. private function addData($commonString) {
  46. $this->data = array();
  47. for($i = 0; $i < REDIS_ARRAY_DATA_SIZE; $i++) {
  48. $k = rand().'_'.$commonString.'_'.rand();
  49. $this->data[$k] = rand();
  50. }
  51. $this->ra->mset($this->data);
  52. }
  53. private function checkCommonLocality() {
  54. // check that they're all on the same node.
  55. $lastNode = NULL;
  56. foreach($this->data as $k => $v) {
  57. $node = $this->ra->_target($k);
  58. if($lastNode) {
  59. $this->assertTrue($node === $lastNode);
  60. }
  61. $this->assertTrue($this->ra->get($k) == $v);
  62. $lastNode = $node;
  63. }
  64. }
  65. public function testKeyLocality() {
  66. // basic key locality with default hash
  67. $this->addData('{hashed part of the key}');
  68. $this->checkCommonLocality();
  69. // with common hashing function
  70. global $newRing, $oldRing, $useIndex;
  71. $this->ra = new RedisArray($newRing, array('previous' => $oldRing,
  72. 'index' => $useIndex,
  73. 'function' => 'custom_hash'));
  74. // basic key locality with custom hash
  75. $this->addData('fb'.rand());
  76. $this->checkCommonLocality();
  77. }
  78. public function customDistributor($key)
  79. {
  80. $a = unpack("N*", md5($key, true));
  81. global $newRing;
  82. $pos = abs($a[1]) % count($newRing);
  83. return $pos;
  84. }
  85. public function testKeyDistributor()
  86. {
  87. global $newRing, $useIndex;
  88. $this->ra = new RedisArray($newRing, array(
  89. 'index' => $useIndex,
  90. 'function' => 'custom_hash',
  91. 'distributor' => array($this, "customDistributor")));
  92. // custom key distribution function.
  93. $this->addData('fb'.rand());
  94. // check that they're all on the expected node.
  95. $lastNode = NULL;
  96. foreach($this->data as $k => $v) {
  97. $node = $this->ra->_target($k);
  98. $pos = $this->customDistributor($k);
  99. $this->assertTrue($node === $newRing[$pos]);
  100. }
  101. }
  102. }
  103. class Redis_Rehashing_Test extends TestSuite
  104. {
  105. public $ra = NULL;
  106. private $useIndex;
  107. // data
  108. private $strings;
  109. private $sets;
  110. private $lists;
  111. private $hashes;
  112. private $zsets;
  113. public function setUp() {
  114. // initialize strings.
  115. $n = REDIS_ARRAY_DATA_SIZE;
  116. $this->strings = array();
  117. for($i = 0; $i < $n; $i++) {
  118. $this->strings['key-'.$i] = 'val-'.$i;
  119. }
  120. // initialize sets
  121. for($i = 0; $i < $n; $i++) {
  122. // each set has 20 elements
  123. $this->sets['set-'.$i] = range($i, $i+20);
  124. }
  125. // initialize lists
  126. for($i = 0; $i < $n; $i++) {
  127. // each list has 20 elements
  128. $this->lists['list-'.$i] = range($i, $i+20);
  129. }
  130. // initialize hashes
  131. for($i = 0; $i < $n; $i++) {
  132. // each hash has 5 keys
  133. $this->hashes['hash-'.$i] = array('A' => $i, 'B' => $i+1, 'C' => $i+2, 'D' => $i+3, 'E' => $i+4);
  134. }
  135. // initialize sorted sets
  136. for($i = 0; $i < $n; $i++) {
  137. // each sorted sets has 5 elements
  138. $this->zsets['zset-'.$i] = array($i, 'A', $i+1, 'B', $i+2, 'C', $i+3, 'D', $i+4, 'E');
  139. }
  140. global $newRing, $oldRing, $useIndex;
  141. // create array
  142. $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex));
  143. }
  144. public function testFlush() {
  145. // flush all servers first.
  146. global $serverList;
  147. foreach($serverList as $s) {
  148. list($host, $port) = explode(':', $s);
  149. $r = new Redis;
  150. $r->pconnect($host, (int)$port);
  151. $r->flushdb();
  152. }
  153. }
  154. private function distributeKeys() {
  155. // strings
  156. foreach($this->strings as $k => $v) {
  157. $this->ra->set($k, $v);
  158. }
  159. // sets
  160. foreach($this->sets as $k => $v) {
  161. call_user_func_array(array($this->ra, 'sadd'), array_merge(array($k), $v));
  162. }
  163. // lists
  164. foreach($this->lists as $k => $v) {
  165. call_user_func_array(array($this->ra, 'rpush'), array_merge(array($k), $v));
  166. }
  167. // hashes
  168. foreach($this->hashes as $k => $v) {
  169. $this->ra->hmset($k, $v);
  170. }
  171. // sorted sets
  172. foreach($this->zsets as $k => $v) {
  173. call_user_func_array(array($this->ra, 'zadd'), array_merge(array($k), $v));
  174. }
  175. }
  176. public function testDistribution() {
  177. $this->distributeKeys();
  178. }
  179. public function testSimpleRead() {
  180. $this->readAllvalues();
  181. }
  182. private function readAllvalues() {
  183. // strings
  184. foreach($this->strings as $k => $v) {
  185. $this->assertTrue($this->ra->get($k) === $v);
  186. }
  187. // sets
  188. foreach($this->sets as $k => $v) {
  189. $ret = $this->ra->smembers($k); // get values
  190. // sort sets
  191. sort($v);
  192. sort($ret);
  193. $this->assertTrue($ret == $v);
  194. }
  195. // lists
  196. foreach($this->lists as $k => $v) {
  197. $ret = $this->ra->lrange($k, 0, -1);
  198. $this->assertTrue($ret == $v);
  199. }
  200. // hashes
  201. foreach($this->hashes as $k => $v) {
  202. $ret = $this->ra->hgetall($k); // get values
  203. $this->assertTrue($ret == $v);
  204. }
  205. // sorted sets
  206. foreach($this->zsets as $k => $v) {
  207. $ret = $this->ra->zrange($k, 0, -1, TRUE); // get values with scores
  208. // create assoc array from local dataset
  209. $tmp = array();
  210. for($i = 0; $i < count($v); $i += 2) {
  211. $tmp[$v[$i+1]] = $v[$i];
  212. }
  213. // compare to RA value
  214. $this->assertTrue($ret == $tmp);
  215. }
  216. }
  217. // add a new node.
  218. public function testCreateSecondRing() {
  219. global $newRing, $oldRing, $serverList;
  220. $oldRing = $newRing; // back up the original.
  221. $newRing = $serverList; // add a new node to the main ring.
  222. }
  223. public function testReadUsingFallbackMechanism() {
  224. $this->readAllvalues(); // some of the reads will fail and will go to another target node.
  225. }
  226. public function testRehash() {
  227. $this->ra->_rehash(); // this will redistribute the keys
  228. }
  229. public function testRehashWithCallback() {
  230. $total = 0;
  231. $this->ra->_rehash(function ($host, $count) use (&$total) {
  232. $total += $count;
  233. });
  234. $this->assertTrue($total > 0);
  235. }
  236. public function testReadRedistributedKeys() {
  237. $this->readAllvalues(); // we shouldn't have any missed reads now.
  238. }
  239. }
  240. // Test auto-migration of keys
  241. class Redis_Auto_Rehashing_Test extends TestSuite {
  242. public $ra = NULL;
  243. // data
  244. private $strings;
  245. public function setUp() {
  246. // initialize strings.
  247. $n = REDIS_ARRAY_DATA_SIZE;
  248. $this->strings = array();
  249. for($i = 0; $i < $n; $i++) {
  250. $this->strings['key-'.$i] = 'val-'.$i;
  251. }
  252. global $newRing, $oldRing, $useIndex;
  253. // create array
  254. $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex, 'autorehash' => TRUE));
  255. }
  256. public function testDistribute() {
  257. // strings
  258. foreach($this->strings as $k => $v) {
  259. $this->ra->set($k, $v);
  260. }
  261. }
  262. private function readAllvalues() {
  263. foreach($this->strings as $k => $v) {
  264. $this->assertTrue($this->ra->get($k) === $v);
  265. }
  266. }
  267. public function testReadAll() {
  268. $this->readAllvalues();
  269. }
  270. // add a new node.
  271. public function testCreateSecondRing() {
  272. global $newRing, $oldRing, $serverList;
  273. $oldRing = $newRing; // back up the original.
  274. $newRing = $serverList; // add a new node to the main ring.
  275. }
  276. // Read and migrate keys on fallback, causing the whole ring to be rehashed.
  277. public function testReadAndMigrateAll() {
  278. $this->readAllvalues();
  279. }
  280. // Read and migrate keys on fallback, causing the whole ring to be rehashed.
  281. public function testAllKeysHaveBeenMigrated() {
  282. foreach($this->strings as $k => $v) {
  283. // get the target for each key
  284. $target = $this->ra->_target($k);
  285. // connect to the target host
  286. list($host,$port) = split(':', $target);
  287. $r = new Redis;
  288. $r->pconnect($host, $port);
  289. $this->assertTrue($v === $r->get($k)); // check that the key has actually been migrated to the new node.
  290. }
  291. }
  292. }
  293. // Test node-specific multi/exec
  294. class Redis_Multi_Exec_Test extends TestSuite {
  295. public $ra = NULL;
  296. public function setUp() {
  297. global $newRing, $oldRing, $useIndex;
  298. // create array
  299. $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex));
  300. }
  301. public function testInit() {
  302. $this->ra->set('{groups}:managers', 2);
  303. $this->ra->set('{groups}:executives', 3);
  304. $this->ra->set('1_{employee:joe}_name', 'joe');
  305. $this->ra->set('1_{employee:joe}_group', 2);
  306. $this->ra->set('1_{employee:joe}_salary', 2000);
  307. }
  308. public function testKeyDistribution() {
  309. // check that all of joe's keys are on the same instance
  310. $lastNode = NULL;
  311. foreach(array('name', 'group', 'salary') as $field) {
  312. $node = $this->ra->_target('1_{employee:joe}_'.$field);
  313. if($lastNode) {
  314. $this->assertTrue($node === $lastNode);
  315. }
  316. $lastNode = $node;
  317. }
  318. }
  319. public function testMultiExec() {
  320. // Joe gets a promotion
  321. $newGroup = $this->ra->get('{groups}:executives');
  322. $newSalary = 4000;
  323. // change both in a transaction.
  324. $host = $this->ra->_target('{employee:joe}'); // transactions are per-node, so we need a reference to it.
  325. $tr = $this->ra->multi($host)
  326. ->set('1_{employee:joe}_group', $newGroup)
  327. ->set('1_{employee:joe}_salary', $newSalary)
  328. ->exec();
  329. // check that the group and salary have been changed
  330. $this->assertTrue($this->ra->get('1_{employee:joe}_group') === $newGroup);
  331. $this->assertTrue($this->ra->get('1_{employee:joe}_salary') == $newSalary);
  332. }
  333. public function testMultiExecMSet() {
  334. global $newGroup, $newSalary;
  335. $newGroup = 1;
  336. $newSalary = 10000;
  337. // test MSET, making Joe a top-level executive
  338. $out = $this->ra->multi($this->ra->_target('{employee:joe}'))
  339. ->mset(array('1_{employee:joe}_group' => $newGroup, '1_{employee:joe}_salary' => $newSalary))
  340. ->exec();
  341. $this->assertTrue($out[0] === TRUE);
  342. }
  343. public function testMultiExecMGet() {
  344. global $newGroup, $newSalary;
  345. // test MGET
  346. $out = $this->ra->multi($this->ra->_target('{employee:joe}'))
  347. ->mget(array('1_{employee:joe}_group', '1_{employee:joe}_salary'))
  348. ->exec();
  349. $this->assertTrue($out[0][0] == $newGroup);
  350. $this->assertTrue($out[0][1] == $newSalary);
  351. }
  352. public function testMultiExecDel() {
  353. // test DEL
  354. $out = $this->ra->multi($this->ra->_target('{employee:joe}'))
  355. ->del('1_{employee:joe}_group', '1_{employee:joe}_salary')
  356. ->exec();
  357. $this->assertTrue($out[0] === 2);
  358. $this->assertTrue($this->ra->exists('1_{employee:joe}_group') === FALSE);
  359. $this->assertTrue($this->ra->exists('1_{employee:joe}_salary') === FALSE);
  360. }
  361. public function testDiscard() {
  362. /* phpredis issue #87 */
  363. $key = 'test_err';
  364. $this->assertTrue($this->ra->set($key, 'test'));
  365. $this->assertTrue('test' === $this->ra->get($key));
  366. $this->ra->watch($key);
  367. // After watch, same
  368. $this->assertTrue('test' === $this->ra->get($key));
  369. // change in a multi/exec block.
  370. $ret = $this->ra->multi($this->ra->_target($key))->set($key, 'test1')->exec();
  371. $this->assertTrue($ret === array(true));
  372. // Get after exec, 'test1':
  373. $this->assertTrue($this->ra->get($key) === 'test1');
  374. $this->ra->watch($key);
  375. // After second watch, still test1.
  376. $this->assertTrue($this->ra->get($key) === 'test1');
  377. $ret = $this->ra->multi($this->ra->_target($key))->set($key, 'test2')->discard();
  378. // Ret after discard: NULL";
  379. $this->assertTrue($ret === NULL);
  380. // Get after discard, unchanged:
  381. $this->assertTrue($this->ra->get($key) === 'test1');
  382. }
  383. }
  384. function run_tests($className) {
  385. // reset rings
  386. global $newRing, $oldRing, $serverList;
  387. $newRing = array('localhost:6379', 'localhost:6380', 'localhost:6381');
  388. $oldRing = array();
  389. $serverList = array('localhost:6379', 'localhost:6380', 'localhost:6381', 'localhost:6382');
  390. // run
  391. TestSuite::run($className);
  392. }
  393. define('REDIS_ARRAY_DATA_SIZE', 1000);
  394. global $useIndex;
  395. foreach(array(true, false) as $useIndex) {
  396. echo "\n".($useIndex?"WITH":"WITHOUT"). " per-node index:\n";
  397. run_tests('Redis_Array_Test');
  398. run_tests('Redis_Rehashing_Test');
  399. run_tests('Redis_Auto_Rehashing_Test');
  400. run_tests('Redis_Multi_Exec_Test');
  401. }
  402. ?>