FileCacheTest.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. <?php
  2. namespace Doctrine\Tests\Common\Cache;
  3. use Doctrine\Common\Cache\Cache;
  4. /**
  5. * @group DCOM-101
  6. */
  7. class FileCacheTest extends \Doctrine\Tests\DoctrineTestCase
  8. {
  9. /**
  10. * @var \Doctrine\Common\Cache\FileCache
  11. */
  12. private $driver;
  13. protected function setUp()
  14. {
  15. $this->driver = $this->getMock(
  16. 'Doctrine\Common\Cache\FileCache',
  17. array('doFetch', 'doContains', 'doSave'),
  18. array(), '', false
  19. );
  20. }
  21. public function testFilenameShouldCreateThePathWithOneSubDirectory()
  22. {
  23. $cache = $this->driver;
  24. $method = new \ReflectionMethod($cache, 'getFilename');
  25. $key = 'item-key';
  26. $expectedDir = array(
  27. '84',
  28. );
  29. $expectedDir = implode(DIRECTORY_SEPARATOR, $expectedDir);
  30. $method->setAccessible(true);
  31. $path = $method->invoke($cache, $key);
  32. $dirname = pathinfo($path, PATHINFO_DIRNAME);
  33. $this->assertEquals(DIRECTORY_SEPARATOR . $expectedDir, $dirname);
  34. }
  35. public function testFileExtensionCorrectlyEscaped()
  36. {
  37. $driver1 = $this->getMock(
  38. 'Doctrine\Common\Cache\FileCache',
  39. array('doFetch', 'doContains', 'doSave'),
  40. array(__DIR__, '.*')
  41. );
  42. $driver2 = $this->getMock(
  43. 'Doctrine\Common\Cache\FileCache',
  44. array('doFetch', 'doContains', 'doSave'),
  45. array(__DIR__, '.php')
  46. );
  47. $doGetStats = new \ReflectionMethod($driver1, 'doGetStats');
  48. $doGetStats->setAccessible(true);
  49. $stats1 = $doGetStats->invoke($driver1);
  50. $stats2 = $doGetStats->invoke($driver2);
  51. $this->assertSame(0, $stats1[Cache::STATS_MEMORY_USAGE]);
  52. $this->assertGreaterThan(0, $stats2[Cache::STATS_MEMORY_USAGE]);
  53. }
  54. /**
  55. * @group DCOM-266
  56. */
  57. public function testFileExtensionSlashCorrectlyEscaped()
  58. {
  59. $driver = $this->getMock(
  60. 'Doctrine\Common\Cache\FileCache',
  61. array('doFetch', 'doContains', 'doSave'),
  62. array(__DIR__ . '/../', DIRECTORY_SEPARATOR . basename(__FILE__))
  63. );
  64. $doGetStats = new \ReflectionMethod($driver, 'doGetStats');
  65. $doGetStats->setAccessible(true);
  66. $stats = $doGetStats->invoke($driver);
  67. $this->assertGreaterThan(0, $stats[Cache::STATS_MEMORY_USAGE]);
  68. }
  69. public function testNonIntUmaskThrowsInvalidArgumentException()
  70. {
  71. $this->setExpectedException('InvalidArgumentException');
  72. $this->getMock(
  73. 'Doctrine\Common\Cache\FileCache',
  74. array('doFetch', 'doContains', 'doSave'),
  75. array('', '', 'invalid')
  76. );
  77. }
  78. public function testGetDirectoryReturnsRealpathDirectoryString()
  79. {
  80. $directory = __DIR__ . '/../';
  81. $driver = $this->getMock(
  82. 'Doctrine\Common\Cache\FileCache',
  83. array('doFetch', 'doContains', 'doSave'),
  84. array($directory)
  85. );
  86. $doGetDirectory = new \ReflectionMethod($driver, 'getDirectory');
  87. $actualDirectory = $doGetDirectory->invoke($driver);
  88. $expectedDirectory = realpath($directory);
  89. $this->assertEquals($expectedDirectory, $actualDirectory);
  90. }
  91. public function testGetExtensionReturnsExtensionString()
  92. {
  93. $directory = __DIR__ . '/../';
  94. $extension = DIRECTORY_SEPARATOR . basename(__FILE__);
  95. $driver = $this->getMock(
  96. 'Doctrine\Common\Cache\FileCache',
  97. array('doFetch', 'doContains', 'doSave'),
  98. array($directory, $extension)
  99. );
  100. $doGetExtension = new \ReflectionMethod($driver, 'getExtension');
  101. $actualExtension = $doGetExtension->invoke($driver);
  102. $this->assertEquals($extension, $actualExtension);
  103. }
  104. const WIN_MAX_PATH_LEN = 258;
  105. public static function getBasePathForWindowsPathLengthTests($pathLength)
  106. {
  107. // Not using __DIR__ because it can get screwed up when xdebug debugger is attached.
  108. $basePath = realpath(sys_get_temp_dir()) . '/' . uniqid('doctrine-cache', true);
  109. /** @noinspection MkdirRaceConditionInspection */
  110. @mkdir($basePath);
  111. $basePath = realpath($basePath);
  112. // Test whether the desired path length is odd or even.
  113. $desiredPathLengthIsOdd = ($pathLength % 2) == 1;
  114. // If the cache key is not too long, the filecache codepath will add
  115. // a slash and bin2hex($key). The length of the added portion will be an odd number.
  116. // len(desired) = len(base path) + len(slash . bin2hex($key))
  117. // odd = even + odd
  118. // even = odd + odd
  119. $basePathLengthShouldBeOdd = !$desiredPathLengthIsOdd;
  120. $basePathLengthIsOdd = (strlen($basePath) % 2) == 1;
  121. // If the base path needs to be odd or even where it is not, we add an odd number of
  122. // characters as a pad. In this case, we're adding '\aa' (or '/aa' depending on platform)
  123. // This is all to make it so that the key we're testing would result in
  124. // a path that is exactly the length we want to test IF the path length limit
  125. // were not in place in FileCache.
  126. if ($basePathLengthIsOdd != $basePathLengthShouldBeOdd) {
  127. $basePath .= DIRECTORY_SEPARATOR . "aa";
  128. }
  129. return $basePath;
  130. }
  131. /**
  132. * @param int $length
  133. * @param string $basePath
  134. *
  135. * @return array
  136. */
  137. public static function getKeyAndPathFittingLength($length, $basePath)
  138. {
  139. $baseDirLength = strlen($basePath);
  140. $extensionLength = strlen('.doctrine.cache');
  141. $directoryLength = strlen(DIRECTORY_SEPARATOR . 'aa' . DIRECTORY_SEPARATOR);
  142. $keyLength = $length - ($baseDirLength + $extensionLength + $directoryLength); // - 1 because of slash
  143. $key = str_repeat('a', floor($keyLength / 2));
  144. $keyHash = hash('sha256', $key);
  145. $keyPath = $basePath
  146. . DIRECTORY_SEPARATOR
  147. . substr($keyHash, 0, 2)
  148. . DIRECTORY_SEPARATOR
  149. . bin2hex($key)
  150. . '.doctrine.cache';
  151. $hashedKeyPath = $basePath
  152. . DIRECTORY_SEPARATOR
  153. . substr($keyHash, 0, 2)
  154. . DIRECTORY_SEPARATOR
  155. . '_' . $keyHash
  156. . '.doctrine.cache';
  157. return array($key, $keyPath, $hashedKeyPath);
  158. }
  159. public function getPathLengthsToTest()
  160. {
  161. // Windows officially supports 260 bytes including null terminator
  162. // 259 characters is too large due to PHP bug (https://bugs.php.net/bug.php?id=70943)
  163. // 260 characters is too large - null terminator is included in allowable length
  164. return array(
  165. array(257, false),
  166. array(258, false),
  167. array(259, true),
  168. array(260, true)
  169. );
  170. }
  171. /**
  172. * @runInSeparateProcess
  173. * @dataProvider getPathLengthsToTest
  174. *
  175. * @covers \Doctrine\Common\Cache\FileCache::getFilename
  176. *
  177. * @param int $length
  178. * @param bool $pathShouldBeHashed
  179. */
  180. public function testWindowsPathLengthLimitationsAreCorrectlyRespected($length, $pathShouldBeHashed)
  181. {
  182. if (! defined('PHP_WINDOWS_VERSION_BUILD')) {
  183. define('PHP_WINDOWS_VERSION_BUILD', 'Yes, this is the "usual suspect", with the usual limitations');
  184. }
  185. $basePath = self::getBasePathForWindowsPathLengthTests($length);
  186. $fileCache = $this->getMockForAbstractClass(
  187. 'Doctrine\Common\Cache\FileCache',
  188. array($basePath, '.doctrine.cache')
  189. );
  190. list($key, $keyPath, $hashedKeyPath) = self::getKeyAndPathFittingLength($length, $basePath);
  191. $getFileName = new \ReflectionMethod($fileCache, 'getFilename');
  192. $getFileName->setAccessible(true);
  193. $this->assertEquals(
  194. $length,
  195. strlen($keyPath),
  196. sprintf('Path expected to be %d characters long is %d characters long', $length, strlen($keyPath))
  197. );
  198. if ($pathShouldBeHashed) {
  199. $keyPath = $hashedKeyPath;
  200. }
  201. if ($pathShouldBeHashed) {
  202. $this->assertSame(
  203. $hashedKeyPath,
  204. $getFileName->invoke($fileCache, $key),
  205. 'Keys should be hashed correctly if they are over the limit.'
  206. );
  207. } else {
  208. $this->assertSame(
  209. $keyPath,
  210. $getFileName->invoke($fileCache, $key),
  211. 'Keys below limit of the allowed length are used directly, unhashed'
  212. );
  213. }
  214. }
  215. }