Skip to content

Commit

Permalink
Enabled testing of APCU for all PHP versions.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
boenrobot committed Aug 17, 2023
1 parent de90587 commit 01a80cb
Show file tree
Hide file tree
Showing 12 changed files with 107 additions and 83 deletions.
14 changes: 8 additions & 6 deletions .github/workflows/phpunit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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: |
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions library/Zend/Cache/Backend/Apc.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand Down
9 changes: 8 additions & 1 deletion library/Zend/Cache/Backend/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -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;
Expand Down
41 changes: 34 additions & 7 deletions library/Zend/Cache/Backend/TwoLevels.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand All @@ -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']);
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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();
Expand Down
8 changes: 4 additions & 4 deletions tests/Zend/Cache/AllTests.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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');
Expand Down
12 changes: 0 additions & 12 deletions tests/Zend/Cache/ApcBackendTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -175,39 +175,27 @@ public function testGetTags()
{
}

/**
* @doesNotPerformAssertions
*/
public function testSaveCorrectCall()
{
$this->_instance->setDirectives(['logging' => false]);
parent::testSaveCorrectCall();
$this->_instance->setDirectives(['logging' => true]);
}

/**
* @doesNotPerformAssertions
*/
public function testSaveWithNullLifeTime()
{
$this->_instance->setDirectives(['logging' => false]);
parent::testSaveWithNullLifeTime();
$this->_instance->setDirectives(['logging' => true]);
}

/**
* @doesNotPerformAssertions
*/
public function testSaveWithSpecificLifeTime()
{
$this->_instance->setDirectives(['logging' => false]);
parent::testSaveWithSpecificLifeTime();
$this->_instance->setDirectives(['logging' => true]);
}

/**
* @doesNotPerformAssertions
*/
public function testGetMetadatas($notag = true)
{
parent::testGetMetadatas($notag);
Expand Down
21 changes: 21 additions & 0 deletions tests/Zend/Cache/FileBackendTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -200,4 +200,25 @@ public function testShouldDeleteOldMetadataFiles()
$this->assertTrue($this->_instance->clean(Zend_Cache::CLEANING_MODE_ALL));
$this->assertFalse(file_exists($fn));
}

public function testSaveCorrectCall()
{
$this->_instance->setDirectives(['logging' => false]);
parent::testSaveCorrectCall();
$this->_instance->setDirectives(['logging' => true]);
}

public function testSaveWithNullLifeTime()
{
$this->_instance->setDirectives(['logging' => false]);
parent::testSaveWithNullLifeTime();
$this->_instance->setDirectives(['logging' => true]);
}

public function testSaveWithSpecificLifeTime()
{
$this->_instance->setDirectives(['logging' => false]);
parent::testSaveWithSpecificLifeTime();
$this->_instance->setDirectives(['logging' => true]);
}
}
39 changes: 27 additions & 12 deletions tests/Zend/Cache/TwoLevelsBackendTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand Down
12 changes: 0 additions & 12 deletions tests/Zend/Cache/WinCacheBackendTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -198,39 +198,27 @@ public function testGetTags()
$this->markTestSkipped('This test skipped due to limitations in this adapter.');
}

/**
* @doesNotPerformAssertions
*/
public function testSaveCorrectCall()
{
$this->_instance->setDirectives(['logging' => false]);
parent::testSaveCorrectCall();
$this->_instance->setDirectives(['logging' => true]);
}

/**
* @doesNotPerformAssertions
*/
public function testSaveWithNullLifeTime()
{
$this->_instance->setDirectives(['logging' => false]);
parent::testSaveWithNullLifeTime();
$this->_instance->setDirectives(['logging' => true]);
}

/**
* @doesNotPerformAssertions
*/
public function testSaveWithSpecificLifeTime()
{
$this->_instance->setDirectives(['logging' => false]);
parent::testSaveWithSpecificLifeTime();
$this->_instance->setDirectives(['logging' => true]);
}

/**
* @doesNotPerformAssertions
*/
public function testGetMetadatas($notag = true)
{
parent::testGetMetadatas($notag);
Expand Down
Loading

0 comments on commit 01a80cb

Please sign in to comment.