diff options
author | Pavlo Yatsukhnenko <yatsukhnenko@gmail.com> | 2018-03-24 18:45:51 +0300 |
---|---|---|
committer | Pavlo Yatsukhnenko <yatsukhnenko@gmail.com> | 2018-03-24 18:45:51 +0300 |
commit | 300c72510c48e210338826b713f260a4eda8abc7 (patch) | |
tree | 2addd5f65234763f6ea0ee72c48b97000b3894e8 /tests | |
parent | fd93e26fc42ae0be6c5d5510516933e7d9350722 (diff) | |
parent | 29edc7db158412239aa5f36a63f342fdcac6c13c (diff) |
Merge branch 'session-locking' into develop
Conflicts:
redis_session.c
tests/RedisTest.php
Diffstat (limited to 'tests')
-rw-r--r-- | tests/RedisClusterTest.php | 20 | ||||
-rw-r--r-- | tests/RedisTest.php | 376 | ||||
-rw-r--r-- | tests/getSessionData.php | 19 | ||||
-rw-r--r-- | tests/regenerateSessionId.php | 83 | ||||
-rw-r--r-- | tests/startSession.php | 38 |
5 files changed, 528 insertions, 8 deletions
diff --git a/tests/RedisClusterTest.php b/tests/RedisClusterTest.php index eb364dbc..f841fa26 100644 --- a/tests/RedisClusterTest.php +++ b/tests/RedisClusterTest.php @@ -23,6 +23,11 @@ class Redis_Cluster_Test extends Redis_Test { RedisCluster::FAILOVER_DISTRIBUTE ); + /** + * @var string + */ + protected $sessionPrefix = 'PHPREDIS_CLUSTER_SESSION:'; + /* Tests we'll skip all together in the context of RedisCluster. The * RedisCluster class doesn't implement specialized (non-redis) commands * such as sortAsc, or sortDesc and other commands such as SELECT are @@ -37,6 +42,21 @@ class Redis_Cluster_Test extends Redis_Test { public function testSwapDB() { return $this->markTestSkipped(); } public function testConnectException() { return $this->markTestSkipped(); } + /* Session locking feature is currently not supported in in context of Redis Cluster. + The biggest issue for this is the distribution nature of Redis cluster */ + public function testSession_savedToRedis() { return $this->markTestSkipped(); } + public function testSession_lockKeyCorrect() { return $this->markTestSkipped(); } + public function testSession_lockingDisabledByDefault() { return $this->markTestSkipped(); } + public function testSession_lockReleasedOnClose() { return $this->markTestSkipped(); } + public function testSession_ttlMaxExecutionTime() { return $this->markTestSkipped(); } + public function testSession_ttlLockExpire() { return $this->markTestSkipped(); } + public function testSession_lockHoldCheckBeforeWrite_otherProcessHasLock() { return $this->markTestSkipped(); } + public function testSession_lockHoldCheckBeforeWrite_nobodyHasLock() { return $this->markTestSkipped(); } + public function testSession_correctLockRetryCount() { return $this->markTestSkipped(); } + public function testSession_defaultLockRetryCount() { return $this->markTestSkipped(); } + public function testSession_noUnlockOfOtherProcess() { return $this->markTestSkipped(); } + public function testSession_lockWaitTime() { return $this->markTestSkipped(); } + /* Load our seeds on construction */ public function __construct() { $str_nodemap_file = dirname($_SERVER['PHP_SELF']) . '/nodes/nodemap'; diff --git a/tests/RedisTest.php b/tests/RedisTest.php index 4d807a78..0475f867 100644 --- a/tests/RedisTest.php +++ b/tests/RedisTest.php @@ -21,6 +21,11 @@ class Redis_Test extends TestSuite */ public $redis; + /** + * @var string + */ + protected $sessionPrefix = 'PHPREDIS_SESSION:'; + public function setUp() { $this->redis = $this->newInstance(); $info = $this->redis->info(); @@ -5142,15 +5147,176 @@ class Redis_Test extends TestSuite $this->assertEquals($this->redis->lrange('mylist', 0, -1), Array('A','B','C','D')); } - public function testSession() + public function testSession_savedToRedis() { - ini_set('session.save_handler', 'redis'); - ini_set('session.save_path', 'tcp://localhost:6379'); - if (!@session_start()) { - return $this->markTestSkipped(); - } - session_write_close(); - $this->assertTrue($this->redis->exists('PHPREDIS_SESSION:' . session_id())); + $this->setSessionHandler(); + + $sessionId = $this->generateSessionId(); + $sessionSuccessful = $this->startSessionProcess($sessionId, 0, false); + + $this->assertTrue($this->redis->exists($this->sessionPrefix . $sessionId)); + $this->assertTrue($sessionSuccessful); + } + + public function testSession_lockKeyCorrect() + { + $this->setSessionHandler(); + $sessionId = $this->generateSessionId(); + $this->startSessionProcess($sessionId, 5, true); + usleep(100000); + + $this->assertTrue($this->redis->exists($this->sessionPrefix . $sessionId . '_LOCK')); + } + + public function testSession_lockingDisabledByDefault() + { + $this->setSessionHandler(); + $sessionId = $this->generateSessionId(); + $this->startSessionProcess($sessionId, 5, true, 300, false); + usleep(100000); + + $start = microtime(true); + $sessionSuccessful = $sessionSuccessful = $this->startSessionProcess($sessionId, 0, false, 300, false); + $end = microtime(true); + $elapsedTime = $end - $start; + + $this->assertFalse($this->redis->exists($this->sessionPrefix . $sessionId . '_LOCK')); + $this->assertTrue($elapsedTime < 1); + $this->assertTrue($sessionSuccessful); + } + + public function testSession_lockReleasedOnClose() + { + $this->setSessionHandler(); + $sessionId = $this->generateSessionId(); + $this->startSessionProcess($sessionId, 1, true); + usleep(1100000); + + $this->assertFalse($this->redis->exists($this->sessionPrefix . $sessionId . '_LOCK')); + } + + public function testSession_ttlMaxExecutionTime() + { + $this->setSessionHandler(); + $sessionId = $this->generateSessionId(); + $this->startSessionProcess($sessionId, 10, true, 2); + usleep(100000); + + $start = microtime(true); + $sessionSuccessful = $this->startSessionProcess($sessionId, 0, false); + $end = microtime(true); + $elapsedTime = $end - $start; + + $this->assertTrue($elapsedTime < 3); + $this->assertTrue($sessionSuccessful); + } + + public function testSession_ttlLockExpire() + { + $this->setSessionHandler(); + $sessionId = $this->generateSessionId(); + $this->startSessionProcess($sessionId, 10, true, 300, true, null, -1, 2); + usleep(100000); + + $start = microtime(true); + $sessionSuccessful = $this->startSessionProcess($sessionId, 0, false); + $end = microtime(true); + $elapsedTime = $end - $start; + + $this->assertTrue($elapsedTime < 3); + $this->assertTrue($sessionSuccessful); + } + + public function testSession_lockHoldCheckBeforeWrite_otherProcessHasLock() + { + $this->setSessionHandler(); + $sessionId = $this->generateSessionId(); + $this->startSessionProcess($sessionId, 2, true, 300, true, null, -1, 1, 'firstProcess'); + usleep(1500000); // 1.5 sec + $writeSuccessful = $this->startSessionProcess($sessionId, 0, false, 300, true, null, -1, 10, 'secondProcess'); + sleep(1); + + $this->assertTrue($writeSuccessful); + $this->assertEquals('secondProcess', $this->getSessionData($sessionId)); + } + + public function testSession_lockHoldCheckBeforeWrite_nobodyHasLock() + { + $this->setSessionHandler(); + $sessionId = $this->generateSessionId(); + $writeSuccessful = $this->startSessionProcess($sessionId, 2, false, 300, true, null, -1, 1, 'firstProcess'); + + $this->assertFalse($writeSuccessful); + $this->assertTrue('firstProcess' !== $this->getSessionData($sessionId)); + } + + public function testSession_correctLockRetryCount() + { + $this->setSessionHandler(); + $sessionId = $this->generateSessionId(); + $this->startSessionProcess($sessionId, 10, true); + usleep(100000); + + $start = microtime(true); + $sessionSuccessful = $this->startSessionProcess($sessionId, 0, false, 10, true, 1000000, 3); + $end = microtime(true); + $elapsedTime = $end - $start; + + $this->assertTrue($elapsedTime > 3 && $elapsedTime < 4); + $this->assertFalse($sessionSuccessful); + } + + public function testSession_defaultLockRetryCount() + { + $this->setSessionHandler(); + $sessionId = $this->generateSessionId(); + $this->startSessionProcess($sessionId, 10, true); + usleep(100000); + + $start = microtime(true); + $sessionSuccessful = $this->startSessionProcess($sessionId, 0, false, 10, true, 200000, 0); + $end = microtime(true); + $elapsedTime = $end - $start; + + $this->assertTrue($elapsedTime > 2 && $elapsedTime < 3); + $this->assertFalse($sessionSuccessful); + } + + public function testSession_noUnlockOfOtherProcess() + { + $this->setSessionHandler(); + $sessionId = $this->generateSessionId(); + $this->startSessionProcess($sessionId, 3, true, 1); // Process 1 + usleep(100000); + $this->startSessionProcess($sessionId, 5, true); // Process 2 + + $start = microtime(true); + // Waiting until TTL of process 1 ended and process 2 locked the session, + // because is not guaranteed which waiting process gets the next lock + sleep(1); + $sessionSuccessful = $this->startSessionProcess($sessionId, 0, false); + $end = microtime(true); + $elapsedTime = $end - $start; + + $this->assertTrue($elapsedTime > 5); + $this->assertTrue($sessionSuccessful); + } + + public function testSession_lockWaitTime() + { + $this->setSessionHandler(); + $sessionId = $this->generateSessionId(); + $this->startSessionProcess($sessionId, 1, true, 300); + usleep(100000); + + $start = microtime(true); + $sessionSuccessful = $this->startSessionProcess($sessionId, 0, false, 300, true, 3000000); + $end = microtime(true); + $elapsedTime = $end - $start; + + $this->assertTrue($elapsedTime > 2.5); + $this->assertTrue($elapsedTime < 3.5); + $this->assertTrue($sessionSuccessful); } public function testMultipleConnect() { @@ -5175,5 +5341,199 @@ class Redis_Test extends TestSuite $this->assertTrue(strpos($e, "timed out") !== false); } } + + public function testSession_regenerateSessionId_noLock_noDestroy() { + $this->setSessionHandler(); + $sessionId = $this->generateSessionId(); + $this->startSessionProcess($sessionId, 0, false, 300, true, null, -1, 1, 'bar'); + + $newSessionId = $this->regenerateSessionId($sessionId); + + $this->assertTrue($newSessionId !== $sessionId); + $this->assertEquals('bar', $this->getSessionData($newSessionId)); + } + + public function testSession_regenerateSessionId_noLock_withDestroy() { + $this->setSessionHandler(); + $sessionId = $this->generateSessionId(); + $this->startSessionProcess($sessionId, 0, false, 300, true, null, -1, 1, 'bar'); + + $newSessionId = $this->regenerateSessionId($sessionId, false, true); + + $this->assertTrue($newSessionId !== $sessionId); + $this->assertEquals('bar', $this->getSessionData($newSessionId)); + } + + public function testSession_regenerateSessionId_withLock_noDestroy() { + $this->setSessionHandler(); + $sessionId = $this->generateSessionId(); + $this->startSessionProcess($sessionId, 0, false, 300, true, null, -1, 1, 'bar'); + + $newSessionId = $this->regenerateSessionId($sessionId, true); + + $this->assertTrue($newSessionId !== $sessionId); + $this->assertEquals('bar', $this->getSessionData($newSessionId)); + } + + public function testSession_regenerateSessionId_withLock_withDestroy() { + $this->setSessionHandler(); + $sessionId = $this->generateSessionId(); + $this->startSessionProcess($sessionId, 0, false, 300, true, null, -1, 1, 'bar'); + + $newSessionId = $this->regenerateSessionId($sessionId, true, true); + + $this->assertTrue($newSessionId !== $sessionId); + $this->assertEquals('bar', $this->getSessionData($newSessionId)); + } + + public function testSession_regenerateSessionId_noLock_noDestroy_withProxy() { + if (!interface_exists('SessionHandlerInterface')) { + $this->markTestSkipped('session handler interface not available in PHP < 5.4'); + } + + $this->setSessionHandler(); + $sessionId = $this->generateSessionId(); + $this->startSessionProcess($sessionId, 0, false, 300, true, null, -1, 1, 'bar'); + + $newSessionId = $this->regenerateSessionId($sessionId, false, false, true); + + $this->assertTrue($newSessionId !== $sessionId); + $this->assertEquals('bar', $this->getSessionData($newSessionId)); + } + + public function testSession_regenerateSessionId_noLock_withDestroy_withProxy() { + if (!interface_exists('SessionHandlerInterface')) { + $this->markTestSkipped('session handler interface not available in PHP < 5.4'); + } + + $this->setSessionHandler(); + $sessionId = $this->generateSessionId(); + $this->startSessionProcess($sessionId, 0, false, 300, true, null, -1, 1, 'bar'); + + $newSessionId = $this->regenerateSessionId($sessionId, false, true, true); + + $this->assertTrue($newSessionId !== $sessionId); + $this->assertEquals('bar', $this->getSessionData($newSessionId)); + } + + public function testSession_regenerateSessionId_withLock_noDestroy_withProxy() { + if (!interface_exists('SessionHandlerInterface')) { + $this->markTestSkipped('session handler interface not available in PHP < 5.4'); + } + + $this->setSessionHandler(); + $sessionId = $this->generateSessionId(); + $this->startSessionProcess($sessionId, 0, false, 300, true, null, -1, 1, 'bar'); + + $newSessionId = $this->regenerateSessionId($sessionId, true, false, true); + + $this->assertTrue($newSessionId !== $sessionId); + $this->assertEquals('bar', $this->getSessionData($newSessionId)); + } + + public function testSession_regenerateSessionId_withLock_withDestroy_withProxy() { + if (!interface_exists('SessionHandlerInterface')) { + $this->markTestSkipped('session handler interface not available in PHP < 5.4'); + } + + $this->setSessionHandler(); + $sessionId = $this->generateSessionId(); + $this->startSessionProcess($sessionId, 0, false, 300, true, null, -1, 1, 'bar'); + + $newSessionId = $this->regenerateSessionId($sessionId, true, true, true); + + $this->assertTrue($newSessionId !== $sessionId); + $this->assertEquals('bar', $this->getSessionData($newSessionId)); + } + + private function setSessionHandler() + { + $host = $this->getHost() ?: 'localhost'; + + ini_set('session.save_handler', 'redis'); + ini_set('session.save_path', 'tcp://' . $host . ':6379'); + } + + /** + * @return string + */ + private function generateSessionId() + { + if (function_exists('session_create_id')) { + return session_create_id(); + } else { + $encoded = bin2hex(openssl_random_pseudo_bytes(8)); + return $encoded; + } + } + + /** + * @param string $sessionId + * @param int $sleepTime + * @param bool $background + * @param int $maxExecutionTime + * @param bool $locking_enabled + * @param int $lock_wait_time + * @param int $lock_retries + * @param int $lock_expires + * @param string $sessionData + * + * @return bool + */ + private function startSessionProcess($sessionId, $sleepTime, $background, $maxExecutionTime = 300, $locking_enabled = true, $lock_wait_time = null, $lock_retries = -1, $lock_expires = 0, $sessionData = '') + { + if (substr(php_uname(), 0, 7) == "Windows"){ + $this->markTestSkipped(); + return true; + } else { + $commandParameters = array($this->getHost(), $sessionId, $sleepTime, $maxExecutionTime, $lock_retries, $lock_expires, $sessionData); + if ($locking_enabled) { + $commandParameters[] = '1'; + + if ($lock_wait_time != null) { + $commandParameters[] = $lock_wait_time; + } + } + $commandParameters = array_map('escapeshellarg', $commandParameters); + + $command = 'php ' . __DIR__ . '/startSession.php ' . implode(' ', $commandParameters); + $command .= $background ? ' 2>/dev/null > /dev/null &' : ' 2>&1'; + + exec($command, $output); + return ($background || (count($output) == 1 && $output[0] == 'SUCCESS')) ? true : false; + } + } + + /** + * @param string $sessionId + * + * @return string + */ + private function getSessionData($sessionId) + { + $command = 'php ' . __DIR__ . '/getSessionData.php ' . escapeshellarg($this->getHost()) . ' ' . escapeshellarg($sessionId); + exec($command, $output); + + return $output[0]; + } + + /** + * @param string $sessionId + * @param bool $locking + * @param bool $destroyPrevious + * @param bool $sessionProxy + * + * @return string + */ + private function regenerateSessionId($sessionId, $locking = false, $destroyPrevious = false, $sessionProxy = false) + { + $args = array_map('escapeshellarg', array($sessionId, $locking, $destroyPrevious, $sessionProxy)); + + $command = 'php --no-php-ini --define extension=igbinary.so --define extension=' . __DIR__ . '/../modules/redis.so ' . __DIR__ . '/regenerateSessionId.php ' . escapeshellarg($this->getHost()) . ' ' . implode(' ', $args); + + exec($command, $output); + + return $output[0]; + } } ?> diff --git a/tests/getSessionData.php b/tests/getSessionData.php new file mode 100644 index 00000000..b5bea74a --- /dev/null +++ b/tests/getSessionData.php @@ -0,0 +1,19 @@ +<?php +error_reporting(E_ERROR | E_WARNING); + +$redisHost = $argv[1]; +$sessionId = $argv[2]; + +if (empty($redisHost)) { + $redisHost = 'localhost'; +} + +ini_set('session.save_handler', 'redis'); +ini_set('session.save_path', 'tcp://' . $redisHost . ':6379'); + +session_id($sessionId); +if (!session_start()) { + echo "session_start() was nut successful"; +} else { + echo isset($_SESSION['redis_test']) ? $_SESSION['redis_test'] : 'Key redis_test not found'; +} diff --git a/tests/regenerateSessionId.php b/tests/regenerateSessionId.php new file mode 100644 index 00000000..6d49c0ab --- /dev/null +++ b/tests/regenerateSessionId.php @@ -0,0 +1,83 @@ +<?php +error_reporting(E_ERROR | E_WARNING); + +$redisHost = $argv[1]; +$sessionId = $argv[2]; +$locking = !!$argv[3]; +$destroyPrevious = !!$argv[4]; +$sessionProxy = !!$argv[5]; + +if (empty($redisHost)) { + $redisHost = 'localhost'; +} + +ini_set('session.save_handler', 'redis'); +ini_set('session.save_path', 'tcp://' . $redisHost . ':6379'); + +if ($locking) { + ini_set('redis.session.locking_enabled', true); +} + +if (interface_exists('SessionHandlerInterface')) { + class TestHandler implements SessionHandlerInterface + { + /** + * @var SessionHandler + */ + private $handler; + + public function __construct() + { + $this->handler = new SessionHandler(); + } + + public function close() + { + return $this->handler->close(); + } + + public function destroy($session_id) + { + return $this->handler->destroy($session_id); + } + + public function gc($maxlifetime) + { + return $this->handler->gc($maxlifetime); + } + + public function open($save_path, $name) + { + return $this->handler->open($save_path, $name); + } + + public function read($session_id) + { + return $this->handler->read($session_id); + } + + public function write($session_id, $session_data) + { + return $this->handler->write($session_id, $session_data); + } + } +} + +if ($sessionProxy) { + $handler = new TestHandler(); + session_set_save_handler($handler); +} + +session_id($sessionId); +if (!session_start()) { + $result = "FAILED: session_start()"; +} +elseif (!session_regenerate_id($destroyPrevious)) { + $result = "FAILED: session_regenerate_id()"; +} +else { + $result = session_id(); +} +session_write_close(); +echo $result; + diff --git a/tests/startSession.php b/tests/startSession.php new file mode 100644 index 00000000..081a6951 --- /dev/null +++ b/tests/startSession.php @@ -0,0 +1,38 @@ +<?php +error_reporting(E_ERROR | E_WARNING); + +$redisHost = $argv[1]; +$sessionId = $argv[2]; +$sleepTime = $argv[3]; +$maxExecutionTime = $argv[4]; +$lock_retries = $argv[5]; +$lock_expire = $argv[6]; +$sessionData = $argv[7]; + +if (empty($redisHost)) { + $redisHost = 'localhost'; +} + +ini_set('session.save_handler', 'redis'); +ini_set('session.save_path', 'tcp://' . $redisHost . ':6379'); +ini_set('max_execution_time', $maxExecutionTime); +ini_set('redis.session.lock_retries', $lock_retries); +ini_set('redis.session.lock_expire', $lock_expire); + +if (isset($argv[8])) { + ini_set('redis.session.locking_enabled', $argv[8]); +} + +if (isset($argv[9])) { + ini_set('redis.session.lock_wait_time', $argv[9]); +} + +session_id($sessionId); +$sessionStartSuccessful = session_start(); +sleep($sleepTime); +if (!empty($sessionData)) { + $_SESSION['redis_test'] = $sessionData; +} +session_write_close(); + +echo $sessionStartSuccessful ? 'SUCCESS' : 'FAILURE';
\ No newline at end of file |