From 2a2bbcf4baa5ab21c796e12a484e62c4c3a5dc8e Mon Sep 17 00:00:00 2001 From: Vasil Rangelov Date: Thu, 10 Aug 2023 14:10:37 +0300 Subject: [PATCH] Enabled testing of APCU for all PHP versions. In order to accomplish successful passing of tests in all supported PHP versions: - Removed a few instances of erroneous doesNotPerformAssertions annotations. - Changed the detection from "apc" to "apcu". - Added protection for classes unserialization in TwoLevels cache and made it remove the ID from either cache if it was corrupted at rest. - Fixed the testSaveOverwritesIfFastIsFull() method to only partially mock the fast cache. Specifically, mock only the getFillingPercentage() method, and still really execute the rest, rather than just return NULL, which in turn caused the test to fail. - Added debug logs in TwoLevels cache, and warnings in File cache on errors. --- .github/workflows/phpunit.yml | 14 ++++---- library/Zend/Cache/Backend/Apc.php | 4 +-- library/Zend/Cache/Backend/File.php | 9 ++++- library/Zend/Cache/Backend/TwoLevels.php | 41 +++++++++++++++++++---- tests/Zend/Cache/AllTests.php | 8 ++--- tests/Zend/Cache/ApcBackendTest.php | 12 ------- tests/Zend/Cache/TwoLevelsBackendTest.php | 39 ++++++++++++++------- tests/Zend/Cache/WinCacheBackendTest.php | 12 ------- tests/Zend/Cache/XcacheBackendTest.php | 10 +----- tests/Zend/Cache/ZendServerDiskTest.php | 10 +----- tests/Zend/Cache/ZendServerShMemTest.php | 10 +----- 11 files changed, 86 insertions(+), 83 deletions(-) diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 6cdce10d19..b7668ebeaf 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -33,6 +33,8 @@ jobs: TESTS_ZEND_CACHE_LIBMEMCACHED_HOST: 127.0.0.1 TESTS_ZEND_CACHE_LIBMEMCACHED_PORT: 11211 + TESTS_ZEND_CACHE_APC_ENABLED: true + # https://hub.docker.com/r/bitnami/openldap LDAP_ROOT: "dc=example,dc=com" LDAP_ALLOW_ANON_BINDING: false @@ -112,15 +114,15 @@ jobs: #bare for PHP >=7.2 - php-extensions-bare: "none, iconv, json, libxml, xml, dom, simplexml, xmlwriter, tokenizer, mbstring" #full for PHP <= 8.0 - - php-extensions-full: "none, iconv, json, libxml, xml, dom, simplexml, xmlwriter, tokenizer, mbstring, ctype, openssl, curl, gd, posix, pdo_sqlite, pdo_mysql, fileinfo, zip, sqlite, soap, bcmath, igbinary, bz2, lzf, memcached, memcache, ldap, sqlite, mcrypt, rar" + - php-extensions-full: "none, iconv, json, libxml, xml, dom, simplexml, xmlwriter, tokenizer, mbstring, apcu, ctype, openssl, curl, gd, posix, pdo_sqlite, pdo_mysql, fileinfo, zip, sqlite, soap, bcmath, igbinary, bz2, lzf, memcached, memcache, ldap, sqlite, mcrypt, rar" - php-version: "7.1" php-extensions-bare: "none, iconv, json, libxml, xml, dom, simplexml, xmlwriter, tokenizer" - php-version: "8.1" - php-extensions-full: "none, iconv, json, libxml, xml, dom, simplexml, xmlwriter, tokenizer, mbstring, ctype, openssl, curl, gd, posix, pdo_sqlite, pdo_mysql, fileinfo, zip, sqlite, soap, bcmath, igbinary, bz2, lzf, memcached, memcache, ldap, sqlite, mcrypt" + php-extensions-full: "none, iconv, json, libxml, xml, dom, simplexml, xmlwriter, tokenizer, mbstring, apcu, ctype, openssl, curl, gd, posix, pdo_sqlite, pdo_mysql, fileinfo, zip, sqlite, soap, bcmath, igbinary, bz2, lzf, memcached, memcache, ldap, sqlite, mcrypt" - php-version: "8.2" - php-extensions-full: "none, iconv, json, libxml, xml, dom, simplexml, xmlwriter, tokenizer, mbstring, ctype, openssl, curl, gd, posix, pdo_sqlite, pdo_mysql, fileinfo, zip, sqlite, soap, bcmath, igbinary, bz2, lzf, memcached, memcache, ldap, sqlite, mcrypt" + php-extensions-full: "none, iconv, json, libxml, xml, dom, simplexml, xmlwriter, tokenizer, mbstring, apcu, ctype, openssl, curl, gd, posix, pdo_sqlite, pdo_mysql, fileinfo, zip, sqlite, soap, bcmath, igbinary, bz2, lzf, memcached, memcache, ldap, sqlite, mcrypt" - php-version: "8.3" - php-extensions-full: "none, iconv, json, libxml, xml, dom, simplexml, xmlwriter, tokenizer, mbstring, ctype, openssl, curl, gd, posix, pdo_sqlite, pdo_mysql, fileinfo, zip, sqlite, soap, bcmath, igbinary, bz2, lzf, memcached, memcache, ldap, sqlite" + php-extensions-full: "none, iconv, json, libxml, xml, dom, simplexml, xmlwriter, tokenizer, mbstring, apcu, ctype, openssl, curl, gd, posix, pdo_sqlite, pdo_mysql, fileinfo, zip, sqlite, soap, bcmath, igbinary, bz2, lzf, memcached, memcache, ldap, sqlite" experimental: true steps: @@ -159,7 +161,7 @@ jobs: restore-keys: ${{ runner.os }}-${{ matrix.php-version }}-composer- - name: Install dependencies - run: composer install --prefer-dist --no-progress --no-suggest + run: composer install --prefer-dist --no-progress --no-interaction - name: Lint PHP source files run: | @@ -181,7 +183,7 @@ jobs: php-version: ${{ matrix.php-version }} tools: cs2pr extensions: ${{ matrix.php-extensions-full }} - ini-values: ${{ env.PHP_INI_VALUES }} + ini-values: ${{ env.PHP_INI_VALUES }}, apc.enable_cli=1 env: # https://github.com/shivammathur/setup-php/issues/407#issuecomment-773675741 fail-fast: true diff --git a/library/Zend/Cache/Backend/Apc.php b/library/Zend/Cache/Backend/Apc.php index 4f70b44b3c..5d34ab35d3 100644 --- a/library/Zend/Cache/Backend/Apc.php +++ b/library/Zend/Cache/Backend/Apc.php @@ -74,7 +74,7 @@ public function load($id, $doNotTestCacheValidity = false) { $tmp = apcu_fetch($id); if (is_array($tmp)) { - return $tmp[0]; + return $tmp[0] ?? false; } return false; } @@ -89,7 +89,7 @@ public function test($id) { $tmp = apcu_fetch($id); if (is_array($tmp)) { - return $tmp[1]; + return $tmp[1] ?? false; } return false; } diff --git a/library/Zend/Cache/Backend/File.php b/library/Zend/Cache/Backend/File.php index 38c8944ef2..551877c015 100644 --- a/library/Zend/Cache/Backend/File.php +++ b/library/Zend/Cache/Backend/File.php @@ -250,6 +250,7 @@ public function save($data, $id, $tags = [], $specificLifetime = false) $this->_recursiveMkdirAndChmod($id); } if (!is_writable($path)) { + $this->_log('Zend_Cache_Backend_File::save() : path ' . $path . ' is not writable'); return false; } } @@ -1011,14 +1012,20 @@ protected function _filePutContents($file, $string) $result = false; $f = @fopen($file, 'ab+'); if ($f) { - if ($this->_options['file_locking']) @flock($f, LOCK_EX); + if ($this->_options['file_locking']) { + @flock($f, LOCK_EX); + } fseek($f, 0); ftruncate($f, 0); $tmp = @fwrite($f, $string); if (!($tmp === FALSE)) { $result = true; + } else { + $this->_log("Zend_Cache_Backend_File::_filePutContents() : failed to write contents"); } @fclose($f); + } else { + $this->_log("Zend_Cache_Backend_File::_filePutContents() : we can't obtain handle"); } @chmod($file, $this->_options['cache_file_perm']); return $result; diff --git a/library/Zend/Cache/Backend/TwoLevels.php b/library/Zend/Cache/Backend/TwoLevels.php index 9cb04094b2..5353ea6214 100644 --- a/library/Zend/Cache/Backend/TwoLevels.php +++ b/library/Zend/Cache/Backend/TwoLevels.php @@ -195,23 +195,32 @@ public function test($id) */ public function save($data, $id, $tags = [], $specificLifetime = false, $priority = 8) { + $this->_log('Calling two level save', Zend_Log::DEBUG); $usage = $this->_getFastFillingPercentage('saving'); + $this->_log('Fast filling usage percentage on save: (' . gettype($usage) . ') ' . var_export($usage, true), Zend_Log::DEBUG); $boolFast = true; $lifetime = $this->getLifetime($specificLifetime); $preparedData = $this->_prepareData($data, $lifetime, $priority); if (($priority > 0) && (10 * $priority >= $usage)) { + $this->_log('Saving in fast and slow cache', Zend_Log::DEBUG); $fastLifetime = $this->_getFastLifetime($lifetime, $priority); $boolFast = $this->_fastBackend->save($preparedData, $id, [], $fastLifetime); + $this->_log($boolFast ? 'Saving in fast cache is OK' : 'Saving in fast cache is not OK', Zend_Log::DEBUG); $boolSlow = $this->_slowBackend->save($preparedData, $id, $tags, $lifetime); + $this->_log($boolSlow ? 'Saving in slow cache is OK' : 'Saving in slow cache is not OK', Zend_Log::DEBUG); } else { + $this->_log('Saving in slow cache only', Zend_Log::DEBUG); $boolSlow = $this->_slowBackend->save($preparedData, $id, $tags, $lifetime); + $this->_log($boolSlow ? 'Saving in slow cache is OK' : 'Saving in slow cache is not OK', Zend_Log::DEBUG); if ($boolSlow === true) { + $this->_log('Purging from fast cache on save', Zend_Log::DEBUG); $boolFast = $this->_fastBackend->remove($id); if (!$boolFast && !$this->_fastBackend->test($id)) { // some backends return false on remove() even if the key never existed. (and it won't if fast is full) // all we care about is that the key doesn't exist now $boolFast = true; } + $this->_log($boolFast ? 'Successfully purged from fast cache on save' : 'Failed to purge from fast cache on save', Zend_Log::DEBUG); } } @@ -229,18 +238,36 @@ public function save($data, $id, $tags = [], $specificLifetime = false, $priorit */ public function load($id, $doNotTestCacheValidity = false) { + $this->_log('Calling two level load', Zend_Log::DEBUG); $resultFast = $this->_fastBackend->load($id, $doNotTestCacheValidity); - if ($resultFast === false) { + $array = null; + $isFastResult = is_string($resultFast); + if ($isFastResult) { + $array = unserialize($resultFast, ['allowed_classes' => false]); + } + if (!is_array($array)) { + if ($isFastResult) { + // If the fast result data is not an array, it means it got corrupted somehow at rest. + // Remove it before trying the slow one. + $this->_fastBackend->remove($id); + } $resultSlow = $this->_slowBackend->load($id, $doNotTestCacheValidity); - if ($resultSlow === false) { + if (!is_string($resultSlow)) { // there is no cache at all for this id return false; } + $array = unserialize($resultSlow, ['allowed_classes' => false]); + if (!is_array($array)) { + // If the slow result data is not an array, it means it got corrupted somehow at rest. + // Remove it and return false. + $this->_slowBackend->remove($id); + return false; + } + $isFastResult = false; } - $array = $resultFast !== false ? unserialize($resultFast) : unserialize($resultSlow); - //In case no cache entry was found in the FastCache and auto-filling is enabled, copy data to FastCache - if ($resultFast === false && $this->_options['auto_fill_fast_cache']) { + //In case no cache entry was found in the FastCache (or was corrupted) and auto-filling is enabled, copy data to FastCache + if (!$isFastResult && $this->_options['auto_fill_fast_cache']) { $preparedData = $this->_prepareData($array['data'], $array['lifetime'], $array['priority']); $this->_fastBackend->save($preparedData, $id, [], $array['lifetime']); } @@ -461,7 +488,7 @@ public function getCapabilities() } /** - * Prepare a serialized array to store datas and metadatas informations + * Prepare a serialized array to store data and metadata information * * @param string $data data to store * @param int $lifetime original lifetime @@ -524,7 +551,7 @@ public function ___expire($id) private function _getFastFillingPercentage($mode) { - if ($mode == 'saving') { + if ($mode === 'saving') { // mode saving if ($this->_fastBackendFillingPercentage === null) { $this->_fastBackendFillingPercentage = $this->_fastBackend->getFillingPercentage(); diff --git a/tests/Zend/Cache/AllTests.php b/tests/Zend/Cache/AllTests.php index c7d419b2ef..b132a33dcd 100644 --- a/tests/Zend/Cache/AllTests.php +++ b/tests/Zend/Cache/AllTests.php @@ -104,9 +104,9 @@ public static function suite() $skipTest = new Zend_Cache_ApcBackendTest_SkipTests(); $skipTest->message = 'Tests are not enabled in TestConfiguration.php'; $suite->addTest($skipTest); - } elseif (!extension_loaded('apc')) { + } elseif (!extension_loaded('apcu')) { $skipTest = new Zend_Cache_ApcBackendTest_SkipTests(); - $skipTest->message = "Extension 'APC' is not loaded"; + $skipTest->message = "Extension 'apcu' is not loaded"; $suite->addTest($skipTest); } else { $suite->addTestSuite('Zend_Cache_ApcBackendTest'); @@ -218,9 +218,9 @@ public static function suite() $skipTest = new Zend_Cache_TwoLevelsBackendTest_SkipTests(); $skipTest->message = 'Tests are not enabled in TestConfiguration.php'; $suite->addTest($skipTest); - } elseif (!extension_loaded('apc')) { + } elseif (!extension_loaded('apcu')) { $skipTest = new Zend_Cache_TwoLevelsBackendTest_SkipTests(); - $skipTest->message = "Extension 'APC' is not loaded"; + $skipTest->message = "Extension 'apcu' is not loaded"; $suite->addTest($skipTest); } else { $suite->addTestSuite('Zend_Cache_TwoLevelsBackendTest'); diff --git a/tests/Zend/Cache/ApcBackendTest.php b/tests/Zend/Cache/ApcBackendTest.php index c1e66d6ba7..f4cef0c261 100644 --- a/tests/Zend/Cache/ApcBackendTest.php +++ b/tests/Zend/Cache/ApcBackendTest.php @@ -175,9 +175,6 @@ public function testGetTags() { } - /** - * @doesNotPerformAssertions - */ public function testSaveCorrectCall() { $this->_instance->setDirectives(['logging' => false]); @@ -185,9 +182,6 @@ public function testSaveCorrectCall() $this->_instance->setDirectives(['logging' => true]); } - /** - * @doesNotPerformAssertions - */ public function testSaveWithNullLifeTime() { $this->_instance->setDirectives(['logging' => false]); @@ -195,9 +189,6 @@ public function testSaveWithNullLifeTime() $this->_instance->setDirectives(['logging' => true]); } - /** - * @doesNotPerformAssertions - */ public function testSaveWithSpecificLifeTime() { $this->_instance->setDirectives(['logging' => false]); @@ -205,9 +196,6 @@ public function testSaveWithSpecificLifeTime() $this->_instance->setDirectives(['logging' => true]); } - /** - * @doesNotPerformAssertions - */ public function testGetMetadatas($notag = true) { parent::testGetMetadatas($notag); diff --git a/tests/Zend/Cache/TwoLevelsBackendTest.php b/tests/Zend/Cache/TwoLevelsBackendTest.php index 70cc1d30cd..bf1bdbea43 100644 --- a/tests/Zend/Cache/TwoLevelsBackendTest.php +++ b/tests/Zend/Cache/TwoLevelsBackendTest.php @@ -99,30 +99,45 @@ public function testConstructorCorrectCall() public function testSaveOverwritesIfFastIsFull() { $slowBackend = 'File'; - $fastBackend = $this->createMock('Zend_Cache_Backend_Apc'); - $fastBackend->expects($this->at(0)) - ->method('getFillingPercentage') - ->will($this->returnValue(0)); - $fastBackend->expects($this->at(1)) + $fastBackend = $this->createPartialMock('Zend_Cache_Backend_Apc', ['getFillingPercentage']); + $fastBackend->expects($this->exactly(2)) ->method('getFillingPercentage') - ->will($this->returnValue(90)); - + ->willReturn(0, 90); $slowBackendOptions = [ - 'cache_dir' => $this->_cache_dir + 'cache_dir' => $this->_cache_dir, ]; + + $logStream = fopen('php://temp/maxmemory:4194304', 'a+b'); + $logger = new Zend_Log(new Zend_Log_Writer_Stream($logStream)); $cache = new Zend_Cache_Backend_TwoLevels([ 'fast_backend' => $fastBackend, 'slow_backend' => $slowBackend, 'slow_backend_options' => $slowBackendOptions, 'stats_update_factor' => 1 ]); + $cache->setDirectives(['logging' => true, 'logger' => $logger]); $id = 'test' . uniqid(); - $this->assertTrue($cache->save(10, $id)); //fast usage at 0% - - $this->assertTrue($cache->save(100, $id)); //fast usage at 90% - $this->assertEquals(100, $cache->load($id)); + + $saveResult = $cache->save(10, $id); + $failMessage = 'Failed to save when fast usage is 0. Two level logs: ' . "\n" . + stream_get_contents($logStream, -1, 0); + $this->assertTrue($saveResult, $failMessage); //fast usage at 0% + + $logger->debug('Finished saving when usage is at 0'); + + $saveResult = $cache->save(100, $id); + $failMessage = 'Failed to save when fast usage is 90. Two level logs: ' . "\n" . + stream_get_contents($logStream, -1, 0); + $this->assertTrue($saveResult, $failMessage); //fast usage at 90% + + $logger->debug('Finished saving when usage is at 90'); + + $loadResult = $cache->load($id); + $failMessage = 'Failed to load when fast usage is 90. Two level logs: ' . "\n" . + stream_get_contents($logStream, -1, 0); + $this->assertEquals(100, $loadResult, $failMessage); } /** diff --git a/tests/Zend/Cache/WinCacheBackendTest.php b/tests/Zend/Cache/WinCacheBackendTest.php index 15fcf0868a..d9b9ea243e 100644 --- a/tests/Zend/Cache/WinCacheBackendTest.php +++ b/tests/Zend/Cache/WinCacheBackendTest.php @@ -198,9 +198,6 @@ public function testGetTags() $this->markTestSkipped('This test skipped due to limitations in this adapter.'); } - /** - * @doesNotPerformAssertions - */ public function testSaveCorrectCall() { $this->_instance->setDirectives(['logging' => false]); @@ -208,9 +205,6 @@ public function testSaveCorrectCall() $this->_instance->setDirectives(['logging' => true]); } - /** - * @doesNotPerformAssertions - */ public function testSaveWithNullLifeTime() { $this->_instance->setDirectives(['logging' => false]); @@ -218,9 +212,6 @@ public function testSaveWithNullLifeTime() $this->_instance->setDirectives(['logging' => true]); } - /** - * @doesNotPerformAssertions - */ public function testSaveWithSpecificLifeTime() { $this->_instance->setDirectives(['logging' => false]); @@ -228,9 +219,6 @@ public function testSaveWithSpecificLifeTime() $this->_instance->setDirectives(['logging' => true]); } - /** - * @doesNotPerformAssertions - */ public function testGetMetadatas($notag = true) { parent::testGetMetadatas($notag); diff --git a/tests/Zend/Cache/XcacheBackendTest.php b/tests/Zend/Cache/XcacheBackendTest.php index 0147128e6a..69be8afae7 100644 --- a/tests/Zend/Cache/XcacheBackendTest.php +++ b/tests/Zend/Cache/XcacheBackendTest.php @@ -129,9 +129,7 @@ public function testCleanModeNotMatchingTags2() public function testCleanModeNotMatchingTags3() { } - /** - * @doesNotPerformAssertions - */ + public function testSaveCorrectCall() { $this->_instance->setDirectives(['logging' => false]); @@ -139,9 +137,6 @@ public function testSaveCorrectCall() $this->_instance->setDirectives(['logging' => true]); } - /** - * @doesNotPerformAssertions - */ public function testSaveWithNullLifeTime() { $this->_instance->setDirectives(['logging' => false]); @@ -149,9 +144,6 @@ public function testSaveWithNullLifeTime() $this->_instance->setDirectives(['logging' => true]); } - /** - * @doesNotPerformAssertions - */ public function testSaveWithSpecificLifeTime() { $this->_instance->setDirectives(['logging' => false]); diff --git a/tests/Zend/Cache/ZendServerDiskTest.php b/tests/Zend/Cache/ZendServerDiskTest.php index 5d1e26cd43..809eaccc14 100755 --- a/tests/Zend/Cache/ZendServerDiskTest.php +++ b/tests/Zend/Cache/ZendServerDiskTest.php @@ -126,9 +126,7 @@ public function testCleanModeNotMatchingTags2() public function testCleanModeNotMatchingTags3() { } - /** - * @doesNotPerformAssertions - */ + public function testSaveCorrectCall() { $this->_instance->setDirectives(['logging' => false]); @@ -136,9 +134,6 @@ public function testSaveCorrectCall() $this->_instance->setDirectives(['logging' => true]); } - /** - * @doesNotPerformAssertions - */ public function testSaveWithNullLifeTime() { $this->_instance->setDirectives(['logging' => false]); @@ -146,9 +141,6 @@ public function testSaveWithNullLifeTime() $this->_instance->setDirectives(['logging' => true]); } - /** - * @doesNotPerformAssertions - */ public function testSaveWithSpecificLifeTime() { $this->_instance->setDirectives(['logging' => false]); diff --git a/tests/Zend/Cache/ZendServerShMemTest.php b/tests/Zend/Cache/ZendServerShMemTest.php index 4da810fc77..ac532a2ac3 100755 --- a/tests/Zend/Cache/ZendServerShMemTest.php +++ b/tests/Zend/Cache/ZendServerShMemTest.php @@ -126,9 +126,7 @@ public function testCleanModeNotMatchingTags2() public function testCleanModeNotMatchingTags3() { } - /** - * @doesNotPerformAssertions - */ + public function testSaveCorrectCall() { $this->_instance->setDirectives(['logging' => false]); @@ -136,9 +134,6 @@ public function testSaveCorrectCall() $this->_instance->setDirectives(['logging' => true]); } - /** - * @doesNotPerformAssertions - */ public function testSaveWithNullLifeTime() { $this->_instance->setDirectives(['logging' => false]); @@ -146,9 +141,6 @@ public function testSaveWithNullLifeTime() $this->_instance->setDirectives(['logging' => true]); } - /** - * @doesNotPerformAssertions - */ public function testSaveWithSpecificLifeTime() { $this->_instance->setDirectives(['logging' => false]);