AbstractItemTest.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. <?php
  2. /*
  3. * This file is part of the Stash package.
  4. *
  5. * (c) Robert Hafner <tedivm@tedivm.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Stash\Test;
  11. use Stash\Item;
  12. use Stash\Invalidation;
  13. use Stash\Utilities;
  14. use Stash\Driver\Ephemeral;
  15. use Stash\Test\Stubs\PoolGetDriverStub;
  16. /**
  17. * @package Stash
  18. * @author Robert Hafner <tedivm@tedivm.com>
  19. *
  20. * @todo find out why this has to be abstract to work (see https://github.com/tedivm/Stash/pull/10)
  21. */
  22. abstract class AbstractItemTest extends \PHPUnit_Framework_TestCase
  23. {
  24. protected $data = array('string' => 'Hello world!',
  25. 'complexString' => "\t\t\t\tHello\r\n\rWorld!",
  26. 'int' => 4234,
  27. 'negint' => -6534,
  28. 'float' => 1.8358023545,
  29. 'negfloat' => -5.7003249023,
  30. 'false' => false,
  31. 'true' => true,
  32. 'null' => null,
  33. 'array' => array(3, 5, 7),
  34. 'hashmap' => array('one' => 1, 'two' => 2),
  35. 'multidemensional array' => array(array(5345),
  36. array(3, 'hello', false, array('one' => 1, 'two' => 2))
  37. )
  38. );
  39. protected $expiration;
  40. protected $startTime;
  41. private $setup = false;
  42. protected $driver;
  43. protected $itemClass = '\Stash\Item';
  44. public static function tearDownAfterClass()
  45. {
  46. Utilities::deleteRecursive(Utilities::getBaseDirectory());
  47. }
  48. protected function setUp()
  49. {
  50. if (!$this->setup) {
  51. $this->startTime = time();
  52. $this->expiration = $this->startTime + 3600;
  53. $this->data['object'] = new \stdClass();
  54. }
  55. }
  56. /**
  57. * This just makes it slightly easier to extend AbstractCacheTest to
  58. * other Item types.
  59. *
  60. * @return \Stash\Interfaces\ItemInterface
  61. */
  62. protected function getItem()
  63. {
  64. return new $this->itemClass();
  65. }
  66. public function testConstruct($key = array())
  67. {
  68. if (!isset($this->driver)) {
  69. $this->driver = new Ephemeral(array());
  70. }
  71. $item = $this->getItem();
  72. $this->assertTrue(is_a($item, 'Stash\Item'), 'Test object is an instance of Stash');
  73. $poolStub = new PoolGetDriverStub();
  74. $poolStub->setDriver($this->driver);
  75. $item->setPool($poolStub);
  76. $item->setKey($key);
  77. return $item;
  78. }
  79. public function testSetupKey()
  80. {
  81. $keyString = 'this/is/the/key';
  82. $keyArray = array('this', 'is', 'the', 'key');
  83. $keyNormalized = array('cache', 'this', 'is', 'the', 'key');
  84. $stashArray = $this->testConstruct($keyArray);
  85. $this->assertAttributeInternalType('string', 'keyString', $stashArray, 'Argument based keys setup keystring');
  86. $this->assertAttributeInternalType('array', 'key', $stashArray, 'Array based keys setup key');
  87. $returnedKey = $stashArray->getKey();
  88. $this->assertEquals($keyString, $returnedKey, 'getKey returns properly normalized key from array argument.');
  89. }
  90. public function testSet()
  91. {
  92. foreach ($this->data as $type => $value) {
  93. $key = array('base', $type);
  94. $stash = $this->testConstruct($key);
  95. $this->assertAttributeInternalType('string', 'keyString', $stash, 'Argument based keys setup keystring');
  96. $this->assertAttributeInternalType('array', 'key', $stash, 'Argument based keys setup key');
  97. $this->assertTrue($stash->set($value)->save(), 'Driver class able to store data type ' . $type);
  98. }
  99. $item = $this->getItem();
  100. $poolStub = new PoolGetDriverStub();
  101. $poolStub->setDriver(new Ephemeral(array()));
  102. $item->setPool($poolStub);
  103. $this->assertFalse($item->set($this->data), 'Item without key returns false for set.');
  104. }
  105. /**
  106. * @depends testSet
  107. */
  108. public function testGet()
  109. {
  110. foreach ($this->data as $type => $value) {
  111. $key = array('base', $type);
  112. $stash = $this->testConstruct($key);
  113. $stash->set($value)->save();
  114. // new object, but same backend
  115. $stash = $this->testConstruct($key);
  116. $data = $stash->get();
  117. $this->assertEquals($value, $data, 'getData ' . $type . ' returns same item as stored');
  118. }
  119. if (!isset($this->driver)) {
  120. $this->driver = new Ephemeral();
  121. }
  122. $item = $this->getItem();
  123. $poolStub = new PoolGetDriverStub();
  124. $poolStub->setDriver(new Ephemeral());
  125. $item->setPool($poolStub);
  126. $this->assertEquals(null, $item->get(), 'Item without key returns null for get.');
  127. }
  128. public function testGetItemInvalidKey()
  129. {
  130. try {
  131. $item = $this->getItem();
  132. $poolStub = new PoolGetDriverStub();
  133. $poolStub->setDriver(new Ephemeral(array()));
  134. $item->setPool($poolStub);
  135. $item->setKey('This is not an array');
  136. } catch (\Throwable $t) {
  137. return;
  138. } catch (\Exception $expected) {
  139. return;
  140. }
  141. $this->fail('An expected exception has not been raised.');
  142. }
  143. public function testLock()
  144. {
  145. $item = $this->getItem();
  146. $poolStub = new PoolGetDriverStub();
  147. $poolStub->setDriver(new Ephemeral());
  148. $item->setPool($poolStub);
  149. $this->assertFalse($item->lock(), 'Item without key returns false for lock.');
  150. }
  151. public function testInvalidation()
  152. {
  153. $key = array('path', 'to', 'item');
  154. $oldValue = 'oldValue';
  155. $newValue = 'newValue';
  156. $runningStash = $this->testConstruct($key);
  157. $runningStash->set($oldValue)->expiresAfter(-300)->save();
  158. // Test without stampede
  159. $controlStash = $this->testConstruct($key);
  160. $controlStash->setInvalidationMethod(Invalidation::VALUE, $newValue);
  161. $return = $controlStash->get();
  162. $this->assertEquals($oldValue, $return, 'Old value is returned');
  163. $this->assertTrue($controlStash->isMiss());
  164. unset($controlStash);
  165. // Enable stampede control
  166. $runningStash->lock();
  167. $this->assertAttributeEquals(true, 'stampedeRunning', $runningStash, 'Stampede flag is set.');
  168. // Old
  169. $oldStash = $this->testConstruct($key);
  170. $oldStash->setInvalidationMethod(Invalidation::OLD);
  171. $return = $oldStash->get(Invalidation::OLD);
  172. $this->assertEquals($oldValue, $return, 'Old value is returned');
  173. $this->assertFalse($oldStash->isMiss());
  174. unset($oldStash);
  175. // Value
  176. $valueStash = $this->testConstruct($key);
  177. $valueStash->setInvalidationMethod(Invalidation::VALUE, $newValue);
  178. $return = $valueStash->get(Invalidation::VALUE, $newValue);
  179. $this->assertEquals($newValue, $return, 'New value is returned');
  180. $this->assertFalse($valueStash->isMiss());
  181. unset($valueStash);
  182. // Sleep
  183. $sleepStash = $this->testConstruct($key);
  184. $sleepStash->setInvalidationMethod(Invalidation::SLEEP, 250, 2);
  185. $start = microtime(true);
  186. $return = $sleepStash->get();
  187. $end = microtime(true);
  188. $this->assertTrue($sleepStash->isMiss());
  189. $sleepTime = ($end - $start) * 1000;
  190. $this->assertGreaterThan(500, $sleepTime, 'Sleep method sleeps for required time.');
  191. $this->assertLessThan(520, $sleepTime, 'Sleep method does not oversleep.');
  192. unset($sleepStash);
  193. // Unknown - if a random, unknown method is passed for invalidation we should rely on the default method
  194. $unknownStash = $this->testConstruct($key);
  195. $return = $unknownStash->get(78);
  196. $this->assertEquals($oldValue, $return, 'Old value is returned');
  197. $this->assertTrue($unknownStash->isMiss(), 'Cache is marked as miss');
  198. unset($unknownStash);
  199. // Test that storing the cache turns off stampede mode.
  200. $runningStash->set($newValue)->expiresAfter(30)->save();
  201. $this->assertAttributeEquals(false, 'stampedeRunning', $runningStash, 'Stampede flag is off.');
  202. unset($runningStash);
  203. // Precompute - test outside limit
  204. $precomputeStash = $this->testConstruct($key);
  205. $precomputeStash->setInvalidationMethod(Invalidation::PRECOMPUTE, 10);
  206. $return = $precomputeStash->get(Invalidation::PRECOMPUTE, 10);
  207. $this->assertFalse($precomputeStash->isMiss(), 'Cache is marked as hit');
  208. unset($precomputeStash);
  209. // Precompute - test inside limit
  210. $precomputeStash = $this->testConstruct($key);
  211. $precomputeStash->setInvalidationMethod(Invalidation::PRECOMPUTE, 35);
  212. $return = $precomputeStash->get();
  213. $this->assertTrue($precomputeStash->isMiss(), 'Cache is marked as miss');
  214. unset($precomputeStash);
  215. // Test Stampede Flag Expiration
  216. $key = array('stampede', 'expire');
  217. $Item_SPtest = $this->testConstruct($key);
  218. $Item_SPtest->setInvalidationMethod(Invalidation::VALUE, $newValue);
  219. $Item_SPtest->set($oldValue)->expiresAfter(-300)->save();
  220. $Item_SPtest->lock(-5);
  221. $this->assertEquals($oldValue, $Item_SPtest->get(), 'Expired lock is ignored');
  222. }
  223. public function testSetTTLDatetime()
  224. {
  225. $expiration = new \DateTime('now');
  226. $expiration->add(new \DateInterval('P1D'));
  227. $key = array('ttl', 'expiration', 'test');
  228. $stash = $this->testConstruct($key);
  229. $stash->set(array(1, 2, 3, 'apples'))
  230. ->setTTL($expiration)
  231. ->save();
  232. $this->assertLessThanOrEqual($expiration->getTimestamp(), $stash->getExpiration()->getTimestamp());
  233. $stash = $this->testConstruct($key);
  234. $data = $stash->get();
  235. $this->assertEquals(array(1, 2, 3, 'apples'), $data, 'getData returns data stores using a datetime expiration');
  236. $this->assertLessThanOrEqual($expiration->getTimestamp(), $stash->getExpiration()->getTimestamp());
  237. }
  238. public function testSetTTLDateInterval()
  239. {
  240. $interval = new \DateInterval('P1D');
  241. $expiration = new \DateTime('now');
  242. $expiration->add($interval);
  243. $key = array('ttl', 'expiration', 'test');
  244. $stash = $this->testConstruct($key);
  245. $stash->set(array(1, 2, 3, 'apples'))
  246. ->setTTL($interval)
  247. ->save();
  248. $stash = $this->testConstruct($key);
  249. $data = $stash->get();
  250. $this->assertEquals(array(1, 2, 3, 'apples'), $data, 'getData returns data stores using a datetime expiration');
  251. $this->assertLessThanOrEqual($expiration->getTimestamp(), $stash->getExpiration()->getTimestamp());
  252. }
  253. public function testSetTTLNulll()
  254. {
  255. $key = array('ttl', 'expiration', 'test');
  256. $stash = $this->testConstruct($key);
  257. $stash->set(array(1, 2, 3, 'apples'))
  258. ->setTTL(null)
  259. ->save();
  260. $this->assertAttributeEquals(null, 'expiration', $stash);
  261. }
  262. public function testExpiresAt()
  263. {
  264. $expiration = new \DateTime('now');
  265. $expiration->add(new \DateInterval('P1D'));
  266. $key = array('base', 'expiration', 'test');
  267. $stash = $this->testConstruct($key);
  268. $stash->set(array(1, 2, 3, 'apples'))
  269. ->expiresAt($expiration)
  270. ->save();
  271. $stash = $this->testConstruct($key);
  272. $data = $stash->get();
  273. $this->assertEquals(array(1, 2, 3, 'apples'), $data, 'getData returns data stores using a datetime expiration');
  274. $this->assertLessThanOrEqual($expiration->getTimestamp(), $stash->getExpiration()->getTimestamp());
  275. }
  276. /**
  277. * @expectedException Stash\Exception\InvalidArgumentException
  278. * @expectedExceptionMessage expiresAt requires \DateTimeInterface or null
  279. */
  280. public function testExpiresAtException()
  281. {
  282. $stash = $this->testConstruct(array('base', 'expiration', 'test'));
  283. $stash->expiresAt(false);
  284. }
  285. public function testExpiresAfterWithDateTimeInterval()
  286. {
  287. $key = array('base', 'expiration', 'test');
  288. $stash = $this->testConstruct($key);
  289. $stash->set(array(1, 2, 3, 'apples'))
  290. ->expiresAfter(new \DateInterval('P1D'))
  291. ->save();
  292. $stash = $this->testConstruct($key);
  293. $data = $stash->get();
  294. $this->assertEquals(array(1, 2, 3, 'apples'), $data, 'getData returns data stores using a datetime expiration');
  295. }
  296. public function testGetCreation()
  297. {
  298. $creation = new \DateTime('now');
  299. $creation->add(new \DateInterval('PT10S')); // expire 10 seconds after createdOn
  300. $creationTS = $creation->getTimestamp();
  301. $key = array('getCreation', 'test');
  302. $stash = $this->testConstruct($key);
  303. $this->assertFalse($stash->getCreation(), 'no record exists yet, return null');
  304. $stash->set(array('stuff'), $creation)->save();
  305. $stash = $this->testConstruct($key);
  306. $createdOn = $stash->getCreation();
  307. $this->assertInstanceOf('\DateTime', $createdOn, 'getCreation returns DateTime');
  308. $itemCreationTimestamp = $createdOn->getTimestamp();
  309. $this->assertEquals($creationTS - 10, $itemCreationTimestamp, 'createdOn is 10 seconds before expiration');
  310. }
  311. public function testGetExpiration()
  312. {
  313. $expiration = new \DateTime('now');
  314. $expiration->add(new \DateInterval('P1D'));
  315. $expirationTS = $expiration->getTimestamp();
  316. $key = array('getExpiration', 'test');
  317. $stash = $this->testConstruct($key);
  318. $currentDate = new \DateTime();
  319. $returnedDate = $stash->getExpiration();
  320. $this->assertLessThanOrEqual(2, $currentDate->getTimestamp() - $returnedDate->getTimestamp(), 'No record set, return as expired.');
  321. $this->assertLessThanOrEqual(2, $returnedDate->getTimestamp() - $currentDate->getTimestamp(), 'No record set, return as expired.');
  322. #$this->assertFalse($stash->getExpiration(), 'no record exists yet, return null');
  323. $stash->set(array('stuff'))->expiresAt($expiration)->save();
  324. $stash = $this->testConstruct($key);
  325. $itemExpiration = $stash->getExpiration();
  326. $this->assertInstanceOf('\DateTime', $itemExpiration, 'getExpiration returns DateTime');
  327. $itemExpirationTimestamp = $itemExpiration->getTimestamp();
  328. $this->assertLessThanOrEqual($expirationTS, $itemExpirationTimestamp, 'sometime before explicitly set expiration');
  329. }
  330. public function testIsMiss()
  331. {
  332. $stash = $this->testConstruct(array('This', 'Should', 'Fail'));
  333. $this->assertTrue($stash->isMiss(), 'isMiss returns true for missing data');
  334. $data = $stash->get();
  335. $this->assertNull($data, 'getData returns null for missing data');
  336. $key = array('isMiss', 'test');
  337. $stash = $this->testConstruct($key);
  338. $stash->set('testString')->save();
  339. $stash = $this->testConstruct($key);
  340. $this->assertTrue(!$stash->isMiss(), 'isMiss returns false for valid data');
  341. }
  342. public function testIsHit()
  343. {
  344. $stash = $this->testConstruct(array('This', 'Should', 'Fail'));
  345. $this->assertFalse($stash->isHit(), 'isHit returns false for missing data');
  346. $data = $stash->get();
  347. $this->assertNull($data, 'getData returns null for missing data');
  348. $key = array('isHit', 'test');
  349. $stash = $this->testConstruct($key);
  350. $stash->set('testString')->save();
  351. $stash = $this->testConstruct($key);
  352. $this->assertTrue($stash->isHit(), 'isHit returns true for valid data');
  353. }
  354. public function testClear()
  355. {
  356. // repopulate
  357. foreach ($this->data as $type => $value) {
  358. $key = array('base', $type);
  359. $stash = $this->testConstruct($key);
  360. $stash->set($value)->save();
  361. $this->assertAttributeInternalType('string', 'keyString', $stash, 'Argument based keys setup keystring');
  362. $this->assertAttributeInternalType('array', 'key', $stash, 'Argument based keys setup key');
  363. $this->assertTrue($stash->set($value)->save(), 'Driver class able to store data type ' . $type);
  364. }
  365. foreach ($this->data as $type => $value) {
  366. $key = array('base', $type);
  367. // Make sure its actually populated. This has the added bonus of making sure one clear doesn't empty the
  368. // entire cache.
  369. $stash = $this->testConstruct($key);
  370. $data = $stash->get();
  371. $this->assertEquals($value, $data, 'getData ' . $type . ' returns same item as stored after other data is cleared');
  372. // Run the clear, make sure it says it works.
  373. $stash = $this->testConstruct($key);
  374. $this->assertTrue($stash->clear(), 'clear returns true');
  375. // Finally verify that the data has actually been removed.
  376. $stash = $this->testConstruct($key);
  377. $data = $stash->get();
  378. $this->assertNull($data, 'getData ' . $type . ' returns null once deleted');
  379. $this->assertTrue($stash->isMiss(), 'isMiss returns true for deleted data');
  380. }
  381. // repopulate
  382. foreach ($this->data as $type => $value) {
  383. $key = array('base', $type);
  384. $stash = $this->testConstruct($key);
  385. $stash->set($value)->save();
  386. }
  387. // clear
  388. $stash = $this->testConstruct();
  389. $this->assertTrue($stash->clear(), 'clear returns true');
  390. // make sure all the keys are gone.
  391. foreach ($this->data as $type => $value) {
  392. $key = array('base', $type);
  393. // Finally verify that the data has actually been removed.
  394. $stash = $this->testConstruct($key);
  395. $data = $stash->get();
  396. $this->assertNull($data, 'getData ' . $type . ' returns null once deleted');
  397. $this->assertTrue($stash->isMiss(), 'isMiss returns true for deleted data');
  398. }
  399. }
  400. public function testExtend()
  401. {
  402. $this->driver = null;
  403. foreach ($this->data as $type => $value) {
  404. $key = array('base', $type);
  405. $stash = $this->testConstruct();
  406. $stash->clear();
  407. $stash = $this->testConstruct($key);
  408. $stash->set($value, -600)->save();
  409. $stash = $this->testConstruct($key);
  410. $this->assertEquals($stash->extend(), $stash, 'extend returns item object');
  411. $stash->save();
  412. $stash = $this->testConstruct($key);
  413. $data = $stash->get();
  414. $this->assertEquals($value, $data, 'getData ' . $type . ' returns same item as stored and extended');
  415. $this->assertFalse($stash->isMiss(), 'getData ' . $type . ' returns false for isMiss');
  416. }
  417. }
  418. public function testDisable()
  419. {
  420. $stash = $this->testConstruct(array('path', 'to', 'key'));
  421. $stash->disable();
  422. $this->assertDisabledStash($stash);
  423. }
  424. public function testDisableCacheWillNeverCallDriver()
  425. {
  426. $item = $this->getItem();
  427. $poolStub = new PoolGetDriverStub();
  428. $poolStub->setDriver($this->getMockedDriver());
  429. $item->setPool($poolStub);
  430. $item->setKey(array('test', 'key'));
  431. $item->disable();
  432. $this->assertTrue($item->isDisabled());
  433. $this->assertDisabledStash($item);
  434. }
  435. public function testDisableCacheGlobally()
  436. {
  437. Item::$runtimeDisable = true;
  438. $testDriver = $this->getMockedDriver();
  439. $item = $this->getItem();
  440. $poolStub = new PoolGetDriverStub();
  441. $poolStub->setDriver($this->getMockedDriver());
  442. $item->setPool($poolStub);
  443. $item->setKey(array('test', 'key'));
  444. $this->assertDisabledStash($item);
  445. $this->assertTrue($item->isDisabled());
  446. $this->assertFalse($testDriver->wasCalled(), 'Driver was not called after Item was disabled.');
  447. Item::$runtimeDisable = false;
  448. }
  449. private function getMockedDriver()
  450. {
  451. return new \Stash\Test\Stubs\DriverCallCheckStub();
  452. }
  453. private function assertDisabledStash(\Stash\Interfaces\ItemInterface $item)
  454. {
  455. $this->assertEquals($item, $item->set('true'), 'storeData returns self for disabled cache');
  456. $this->assertNull($item->get(), 'getData returns null for disabled cache');
  457. $this->assertFalse($item->clear(), 'clear returns false for disabled cache');
  458. $this->assertTrue($item->isMiss(), 'isMiss returns true for disabled cache');
  459. $this->assertFalse($item->extend(), 'extend returns false for disabled cache');
  460. $this->assertTrue($item->lock(100), 'lock returns true for disabled cache');
  461. }
  462. }