CacheTest.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. <?php
  2. namespace Doctrine\Tests\Common\Cache;
  3. use Doctrine\Common\Cache\Cache;
  4. use ArrayObject;
  5. abstract class CacheTest extends \Doctrine\Tests\DoctrineTestCase
  6. {
  7. /**
  8. * @dataProvider provideDataToCache
  9. */
  10. public function testSetContainsFetchDelete($value)
  11. {
  12. $cache = $this->_getCacheDriver();
  13. // Test saving a value, checking if it exists, and fetching it back
  14. $this->assertTrue($cache->save('key', $value));
  15. $this->assertTrue($cache->contains('key'));
  16. if (is_object($value)) {
  17. $this->assertEquals($value, $cache->fetch('key'), 'Objects retrieved from the cache must be equal but not necessarily the same reference');
  18. } else {
  19. $this->assertSame($value, $cache->fetch('key'), 'Scalar and array data retrieved from the cache must be the same as the original, e.g. same type');
  20. }
  21. // Test deleting a value
  22. $this->assertTrue($cache->delete('key'));
  23. $this->assertFalse($cache->contains('key'));
  24. $this->assertFalse($cache->fetch('key'));
  25. }
  26. /**
  27. * @dataProvider provideDataToCache
  28. */
  29. public function testUpdateExistingEntry($value)
  30. {
  31. $cache = $this->_getCacheDriver();
  32. $this->assertTrue($cache->save('key', 'old-value'));
  33. $this->assertTrue($cache->contains('key'));
  34. $this->assertTrue($cache->save('key', $value));
  35. $this->assertTrue($cache->contains('key'));
  36. if (is_object($value)) {
  37. $this->assertEquals($value, $cache->fetch('key'), 'Objects retrieved from the cache must be equal but not necessarily the same reference');
  38. } else {
  39. $this->assertSame($value, $cache->fetch('key'), 'Scalar and array data retrieved from the cache must be the same as the original, e.g. same type');
  40. }
  41. }
  42. public function testCacheKeyIsCaseSensitive()
  43. {
  44. $cache = $this->_getCacheDriver();
  45. $this->assertTrue($cache->save('key', 'value'));
  46. $this->assertTrue($cache->contains('key'));
  47. $this->assertSame('value', $cache->fetch('key'));
  48. $this->assertFalse($cache->contains('KEY'));
  49. $this->assertFalse($cache->fetch('KEY'));
  50. $cache->delete('KEY');
  51. $this->assertTrue($cache->contains('key', 'Deleting cache item with different case must not affect other cache item'));
  52. }
  53. public function testFetchMultiple()
  54. {
  55. $cache = $this->_getCacheDriver();
  56. $values = $this->provideDataToCache();
  57. $saved = array();
  58. foreach ($values as $key => $value) {
  59. $cache->save($key, $value[0]);
  60. $saved[$key] = $value[0];
  61. }
  62. $keys = array_keys($saved);
  63. $this->assertEquals(
  64. $saved,
  65. $cache->fetchMultiple($keys),
  66. 'Testing fetchMultiple with different data types'
  67. );
  68. $this->assertEquals(
  69. array_slice($saved, 0, 1),
  70. $cache->fetchMultiple(array_slice($keys, 0, 1)),
  71. 'Testing fetchMultiple with a single key'
  72. );
  73. $keysWithNonExisting = array();
  74. $keysWithNonExisting[] = 'non_existing1';
  75. $keysWithNonExisting[] = $keys[0];
  76. $keysWithNonExisting[] = 'non_existing2';
  77. $keysWithNonExisting[] = $keys[1];
  78. $keysWithNonExisting[] = 'non_existing3';
  79. $this->assertEquals(
  80. array_slice($saved, 0, 2),
  81. $cache->fetchMultiple($keysWithNonExisting),
  82. 'Testing fetchMultiple with a subset of keys and mixed with non-existing ones'
  83. );
  84. }
  85. public function testFetchMultipleWithNoKeys()
  86. {
  87. $cache = $this->_getCacheDriver();
  88. $this->assertSame(array(), $cache->fetchMultiple(array()));
  89. }
  90. public function testSaveMultiple()
  91. {
  92. $cache = $this->_getCacheDriver();
  93. $cache->deleteAll();
  94. $data = array_map(function ($value) {
  95. return $value[0];
  96. }, $this->provideDataToCache());
  97. $this->assertTrue($cache->saveMultiple($data));
  98. $keys = array_keys($data);
  99. $this->assertEquals($data, $cache->fetchMultiple($keys));
  100. }
  101. public function provideDataToCache()
  102. {
  103. $obj = new \stdClass();
  104. $obj->foo = 'bar';
  105. $obj2 = new \stdClass();
  106. $obj2->bar = 'foo';
  107. $obj2->obj = $obj;
  108. $obj->obj2 = $obj2;
  109. return array(
  110. 'array' => array(array('one', 2, 3.01)),
  111. 'string' => array('value'),
  112. 'string_invalid_utf8' => array("\xc3\x28"),
  113. 'string_null_byte' => array('with'."\0".'null char'),
  114. 'integer' => array(1),
  115. 'float' => array(1.5),
  116. 'object' => array(new ArrayObject(array('one', 2, 3.01))),
  117. 'object_recursive' => array($obj),
  118. 'true' => array(true),
  119. // the following are considered FALSE in boolean context, but caches should still recognize their existence
  120. 'null' => array(null),
  121. 'false' => array(false),
  122. 'array_empty' => array(array()),
  123. 'string_zero' => array('0'),
  124. 'integer_zero' => array(0),
  125. 'float_zero' => array(0.0),
  126. 'string_empty' => array(''),
  127. );
  128. }
  129. public function testDeleteIsSuccessfulWhenKeyDoesNotExist()
  130. {
  131. $cache = $this->_getCacheDriver();
  132. $cache->delete('key');
  133. $this->assertFalse($cache->contains('key'));
  134. $this->assertTrue($cache->delete('key'));
  135. }
  136. public function testDeleteAll()
  137. {
  138. $cache = $this->_getCacheDriver();
  139. $this->assertTrue($cache->save('key1', 1));
  140. $this->assertTrue($cache->save('key2', 2));
  141. $this->assertTrue($cache->deleteAll());
  142. $this->assertFalse($cache->contains('key1'));
  143. $this->assertFalse($cache->contains('key2'));
  144. }
  145. /**
  146. * @dataProvider provideCacheIds
  147. */
  148. public function testCanHandleSpecialCacheIds($id)
  149. {
  150. $cache = $this->_getCacheDriver();
  151. $this->assertTrue($cache->save($id, 'value'));
  152. $this->assertTrue($cache->contains($id));
  153. $this->assertEquals('value', $cache->fetch($id));
  154. $this->assertTrue($cache->delete($id));
  155. $this->assertFalse($cache->contains($id));
  156. $this->assertFalse($cache->fetch($id));
  157. }
  158. public function testNoCacheIdCollisions()
  159. {
  160. $cache = $this->_getCacheDriver();
  161. $ids = $this->provideCacheIds();
  162. // fill cache with each id having a different value
  163. foreach ($ids as $index => $id) {
  164. $cache->save($id[0], $index);
  165. }
  166. // then check value of each cache id
  167. foreach ($ids as $index => $id) {
  168. $value = $cache->fetch($id[0]);
  169. $this->assertNotFalse($value, sprintf('Failed to retrieve data for cache id "%s".', $id[0]));
  170. if ($index !== $value) {
  171. $this->fail(sprintf('Cache id "%s" collides with id "%s".', $id[0], $ids[$value][0]));
  172. }
  173. }
  174. }
  175. /**
  176. * Returns cache ids with special characters that should still work.
  177. *
  178. * For example, the characters :\/<>"*?| are not valid in Windows filenames. So they must be encoded properly.
  179. * Each cache id should be considered different from the others.
  180. *
  181. * @return array
  182. */
  183. public function provideCacheIds()
  184. {
  185. return array(
  186. array(':'),
  187. array('\\'),
  188. array('/'),
  189. array('<'),
  190. array('>'),
  191. array('"'),
  192. array('*'),
  193. array('?'),
  194. array('|'),
  195. array('['),
  196. array(']'),
  197. array('ä'),
  198. array('a'),
  199. array('é'),
  200. array('e'),
  201. array('.'), // directory traversal
  202. array('..'), // directory traversal
  203. array('-'),
  204. array('_'),
  205. array('$'),
  206. array('%'),
  207. array(' '),
  208. array("\0"),
  209. array(''),
  210. array(str_repeat('a', 300)), // long key
  211. array(str_repeat('a', 113)),
  212. );
  213. }
  214. public function testLifetime()
  215. {
  216. $cache = $this->_getCacheDriver();
  217. $cache->save('expire', 'value', 1);
  218. $this->assertTrue($cache->contains('expire'), 'Data should not be expired yet');
  219. // @TODO should more TTL-based tests pop up, so then we should mock the `time` API instead
  220. sleep(2);
  221. $this->assertFalse($cache->contains('expire'), 'Data should be expired');
  222. }
  223. public function testNoExpire()
  224. {
  225. $cache = $this->_getCacheDriver();
  226. $cache->save('noexpire', 'value', 0);
  227. // @TODO should more TTL-based tests pop up, so then we should mock the `time` API instead
  228. sleep(1);
  229. $this->assertTrue($cache->contains('noexpire'), 'Data with lifetime of zero should not expire');
  230. }
  231. public function testLongLifetime()
  232. {
  233. $cache = $this->_getCacheDriver();
  234. $cache->save('longlifetime', 'value', 30 * 24 * 3600 + 1);
  235. $this->assertTrue($cache->contains('longlifetime'), 'Data with lifetime > 30 days should be accepted');
  236. }
  237. public function testDeleteAllAndNamespaceVersioningBetweenCaches()
  238. {
  239. if ( ! $this->isSharedStorage()) {
  240. $this->markTestSkipped('The cache storage needs to be shared.');
  241. }
  242. $cache1 = $this->_getCacheDriver();
  243. $cache2 = $this->_getCacheDriver();
  244. $this->assertTrue($cache1->save('key1', 1));
  245. $this->assertTrue($cache2->save('key2', 2));
  246. /* Both providers are initialized with the same namespace version, so
  247. * they can see entries set by each other.
  248. */
  249. $this->assertTrue($cache1->contains('key1'));
  250. $this->assertTrue($cache1->contains('key2'));
  251. $this->assertTrue($cache2->contains('key1'));
  252. $this->assertTrue($cache2->contains('key2'));
  253. /* Deleting all entries through one provider will only increment the
  254. * namespace version on that object (and in the cache itself, which new
  255. * instances will use to initialize). The second provider will retain
  256. * its original version and still see stale data.
  257. */
  258. $this->assertTrue($cache1->deleteAll());
  259. $this->assertFalse($cache1->contains('key1'));
  260. $this->assertFalse($cache1->contains('key2'));
  261. $this->assertTrue($cache2->contains('key1'));
  262. $this->assertTrue($cache2->contains('key2'));
  263. /* A new cache provider should not see the deleted entries, since its
  264. * namespace version will be initialized.
  265. */
  266. $cache3 = $this->_getCacheDriver();
  267. $this->assertFalse($cache3->contains('key1'));
  268. $this->assertFalse($cache3->contains('key2'));
  269. }
  270. public function testFlushAll()
  271. {
  272. $cache = $this->_getCacheDriver();
  273. $this->assertTrue($cache->save('key1', 1));
  274. $this->assertTrue($cache->save('key2', 2));
  275. $this->assertTrue($cache->flushAll());
  276. $this->assertFalse($cache->contains('key1'));
  277. $this->assertFalse($cache->contains('key2'));
  278. }
  279. public function testFlushAllAndNamespaceVersioningBetweenCaches()
  280. {
  281. if ( ! $this->isSharedStorage()) {
  282. $this->markTestSkipped('The cache storage needs to be shared.');
  283. }
  284. $cache1 = $this->_getCacheDriver();
  285. $cache2 = $this->_getCacheDriver();
  286. /* Deleting all elements from the first provider should increment its
  287. * namespace version before saving the first entry.
  288. */
  289. $cache1->deleteAll();
  290. $this->assertTrue($cache1->save('key1', 1));
  291. /* The second provider will be initialized with the same namespace
  292. * version upon its first save operation.
  293. */
  294. $this->assertTrue($cache2->save('key2', 2));
  295. /* Both providers have the same namespace version and can see entries
  296. * set by each other.
  297. */
  298. $this->assertTrue($cache1->contains('key1'));
  299. $this->assertTrue($cache1->contains('key2'));
  300. $this->assertTrue($cache2->contains('key1'));
  301. $this->assertTrue($cache2->contains('key2'));
  302. /* Flushing all entries through one cache will remove all entries from
  303. * the cache but leave their namespace version as-is.
  304. */
  305. $this->assertTrue($cache1->flushAll());
  306. $this->assertFalse($cache1->contains('key1'));
  307. $this->assertFalse($cache1->contains('key2'));
  308. $this->assertFalse($cache2->contains('key1'));
  309. $this->assertFalse($cache2->contains('key2'));
  310. /* Inserting a new entry will use the same, incremented namespace
  311. * version, and it will be visible to both providers.
  312. */
  313. $this->assertTrue($cache1->save('key1', 1));
  314. $this->assertTrue($cache1->contains('key1'));
  315. $this->assertTrue($cache2->contains('key1'));
  316. /* A new cache provider will be initialized with the original namespace
  317. * version and not share any visibility with the first two providers.
  318. */
  319. $cache3 = $this->_getCacheDriver();
  320. $this->assertFalse($cache3->contains('key1'));
  321. $this->assertFalse($cache3->contains('key2'));
  322. $this->assertTrue($cache3->save('key3', 3));
  323. $this->assertTrue($cache3->contains('key3'));
  324. }
  325. public function testNamespace()
  326. {
  327. $cache = $this->_getCacheDriver();
  328. $cache->setNamespace('ns1_');
  329. $this->assertTrue($cache->save('key1', 1));
  330. $this->assertTrue($cache->contains('key1'));
  331. $cache->setNamespace('ns2_');
  332. $this->assertFalse($cache->contains('key1'));
  333. }
  334. public function testDeleteAllNamespace()
  335. {
  336. $cache = $this->_getCacheDriver();
  337. $cache->setNamespace('ns1');
  338. $this->assertFalse($cache->contains('key1'));
  339. $cache->save('key1', 'test');
  340. $this->assertTrue($cache->contains('key1'));
  341. $cache->setNamespace('ns2');
  342. $this->assertFalse($cache->contains('key1'));
  343. $cache->save('key1', 'test');
  344. $this->assertTrue($cache->contains('key1'));
  345. $cache->setNamespace('ns1');
  346. $this->assertTrue($cache->contains('key1'));
  347. $cache->deleteAll();
  348. $this->assertFalse($cache->contains('key1'));
  349. $cache->setNamespace('ns2');
  350. $this->assertTrue($cache->contains('key1'));
  351. $cache->deleteAll();
  352. $this->assertFalse($cache->contains('key1'));
  353. }
  354. /**
  355. * @group DCOM-43
  356. */
  357. public function testGetStats()
  358. {
  359. $cache = $this->_getCacheDriver();
  360. $stats = $cache->getStats();
  361. $this->assertArrayHasKey(Cache::STATS_HITS, $stats);
  362. $this->assertArrayHasKey(Cache::STATS_MISSES, $stats);
  363. $this->assertArrayHasKey(Cache::STATS_UPTIME, $stats);
  364. $this->assertArrayHasKey(Cache::STATS_MEMORY_USAGE, $stats);
  365. $this->assertArrayHasKey(Cache::STATS_MEMORY_AVAILABLE, $stats);
  366. }
  367. public function testSaveReturnsTrueWithAndWithoutTTlSet()
  368. {
  369. $cache = $this->_getCacheDriver();
  370. $cache->deleteAll();
  371. $this->assertTrue($cache->save('without_ttl', 'without_ttl'));
  372. $this->assertTrue($cache->save('with_ttl', 'with_ttl', 3600));
  373. }
  374. public function testValueThatIsFalseBooleanIsProperlyRetrieved()
  375. {
  376. $cache = $this->_getCacheDriver();
  377. $cache->deleteAll();
  378. $this->assertTrue($cache->save('key1', false));
  379. $this->assertTrue($cache->contains('key1'));
  380. $this->assertFalse($cache->fetch('key1'));
  381. }
  382. /**
  383. * Return whether multiple cache providers share the same storage.
  384. *
  385. * This is used for skipping certain tests for shared storage behavior.
  386. *
  387. * @return bool
  388. */
  389. protected function isSharedStorage()
  390. {
  391. return true;
  392. }
  393. /**
  394. * @return \Doctrine\Common\Cache\CacheProvider
  395. */
  396. abstract protected function _getCacheDriver();
  397. }